From d46130ab3ed3716db22bdd9881a60ec7e4365b63 Mon Sep 17 00:00:00 2001 From: Daniel Suchy <danny@danysek.cz> Date: Wed, 13 May 2009 10:05:48 +0200 Subject: USB: FTDI-SIO new device ids I would like to have added new device to usbserial/ftdi_sio driver. These ids used USB track device (http://www.l-and-b.dk/access_alt.html). They use differend device IDs, but it works as standard usb-serial conventer. From: Daniel Suchy <danny@danysek.cz> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/ftdi_sio.c | 1 + drivers/usb/serial/ftdi_sio.h | 3 +++ 2 files changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 683304d60615..59c7501840cb 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -673,6 +673,7 @@ static struct usb_device_id id_table_combined [] = { { USB_DEVICE(JETI_VID, JETI_SPC1201_PID) }, { USB_DEVICE(MARVELL_VID, MARVELL_SHEEVAPLUG_PID), .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, + { USB_DEVICE(LARSENBRUSGAARD_VID, LB_ALTITRACK_PID) }, { }, /* Optional parameter entry */ { } /* Terminating entry */ }; diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h index 12330fa1c095..0a09118f315e 100644 --- a/drivers/usb/serial/ftdi_sio.h +++ b/drivers/usb/serial/ftdi_sio.h @@ -33,6 +33,9 @@ #define FTDI_NF_RIC_PID 0x0001 /* Product Id */ #define FTDI_USBX_707_PID 0xF857 /* ADSTech IR Blaster USBX-707 */ +/* Larsen and Brusgaard AltiTrack/USBtrack */ +#define LARSENBRUSGAARD_VID 0x0FD8 +#define LB_ALTITRACK_PID 0x0001 /* www.canusb.com Lawicel CANUSB device */ #define FTDI_CANUSB_PID 0xFFA8 /* Product Id */ -- cgit v1.2.3 From 334f9b0f23c9bb90589213279c9cfe19ebe4c0c6 Mon Sep 17 00:00:00 2001 From: Michele Valzelli <valz@messagenet.it> Date: Wed, 27 May 2009 00:09:12 +0200 Subject: USB: option.c: add Toshiba 3G HSDPA SM-Bus Minicard device id This patch adds support for the Toshiba HSDPA Minicard (which is just a rebranded Novatel EU870D) used in some Toshiba laptops. This is my first patch attempt, I hope I got the conventions right. Signed-off-by: Michele Valzelli <valz@messagenet.it> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/option.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index a16d69fadba1..648c481605b1 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -305,6 +305,10 @@ static int option_resume(struct usb_serial *serial); #define DLINK_PRODUCT_DWM_652 0x3e04 +/* TOSHIBA PRODUCTS */ +#define TOSHIBA_VENDOR_ID 0x0930 +#define TOSHIBA_PRODUCT_HSDPA_MINICARD 0x1302 + static struct usb_device_id option_ids[] = { { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA) }, @@ -523,6 +527,7 @@ static struct usb_device_id option_ids[] = { { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_H10) }, { USB_DEVICE(DLINK_VENDOR_ID, DLINK_PRODUCT_DWM_652) }, { USB_DEVICE(0x1da5, 0x4515) }, /* BenQ H20 */ + { USB_DEVICE(TOSHIBA_VENDOR_ID, TOSHIBA_PRODUCT_HSDPA_MINICARD ) }, /* Toshiba 3G HSDPA == Novatel Expedite EU870D MiniCard */ { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, option_ids); -- cgit v1.2.3 From 003051bfb62513842a9e9efde17afeba46519c95 Mon Sep 17 00:00:00 2001 From: Bryan Wu <cooloney@kernel.org> Date: Tue, 2 Jun 2009 03:25:58 -0400 Subject: usb: misc: SiS usbvga dangle: accept MUSB_HDRC as a fast enough host controller Acked-by: Mike Frysinger <vapier.adi@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/misc/sisusbvga/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/misc/sisusbvga/Kconfig b/drivers/usb/misc/sisusbvga/Kconfig index 7603cbe0865d..30ea7ca6846e 100644 --- a/drivers/usb/misc/sisusbvga/Kconfig +++ b/drivers/usb/misc/sisusbvga/Kconfig @@ -1,7 +1,7 @@ config USB_SISUSBVGA tristate "USB 2.0 SVGA dongle support (Net2280/SiS315)" - depends on USB && USB_EHCI_HCD + depends on USB && (USB_MUSB_HDRC || USB_EHCI_HCD) ---help--- Say Y here if you intend to attach a USB2VGA dongle based on a Net2280 and a SiS315 chip. -- cgit v1.2.3 From a5073b52833e4df8e16c93dc4cbb7e0c558c74a2 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov <sshtylyov@ru.mvista.com> Date: Fri, 27 Mar 2009 12:52:43 -0700 Subject: musb_gadget: fix unhandled endpoint 0 IRQs The gadget EP0 code routinely ignores an interrupt at end of the data phase because of musb_g_ep0_giveback() resetting the state machine to "idle, waiting for SETUP" phase prematurely. The driver also prematurely leaves the status phase on receiving the SetupEnd interrupt. As there were still unhandled endpoint 0 interrupts happening from time to time after fixing these issues, there turned to be yet another culprit: two distinct gadget states collapsed into one. The (missing) state that comes after STATUS IN/OUT states was typically indiscernible from them since the corresponding interrupts tend to happen within too little period of time (due to only a zero-length status packet in between) and so they got coalesced; yet this state is not the same as the next one which is associated with the reception of a SETUP packet. Adding this extra state seems to have fixed the rest of the unhandled interrupts that generic_interrupt() and davinci_interrupt() hid by faking their result and only emitting a debug message -- so, stop doing that. Signed-off-by: Sergei Shtylyov <sshtylyov@ru.mvista.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/davinci.c | 7 +----- drivers/usb/musb/musb_core.c | 8 +------ drivers/usb/musb/musb_core.h | 3 ++- drivers/usb/musb/musb_gadget_ep0.c | 45 +++++++++++++++++++++++++++++++++----- 4 files changed, 43 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/davinci.c b/drivers/usb/musb/davinci.c index 10d11ab113ab..898b52fcff5d 100644 --- a/drivers/usb/musb/davinci.c +++ b/drivers/usb/musb/davinci.c @@ -372,12 +372,7 @@ static irqreturn_t davinci_interrupt(int irq, void *__hci) spin_unlock_irqrestore(&musb->lock, flags); - /* REVISIT we sometimes get unhandled IRQs - * (e.g. ep0). not clear why... - */ - if (retval != IRQ_HANDLED) - DBG(5, "unhandled? %08x\n", tmp); - return IRQ_HANDLED; + return retval; } int musb_platform_set_mode(struct musb *musb, u8 mode) diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 4000cf6d1e81..324459b619f7 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -1481,13 +1481,7 @@ static irqreturn_t generic_interrupt(int irq, void *__hci) spin_unlock_irqrestore(&musb->lock, flags); - /* REVISIT we sometimes get spurious IRQs on g_ep0 - * not clear why... - */ - if (retval != IRQ_HANDLED) - DBG(5, "spurious?\n"); - - return IRQ_HANDLED; + return retval; } #else diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index efb39b5e55b5..c2a776ee1947 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -171,7 +171,8 @@ enum musb_h_ep0_state { /* peripheral side ep0 states */ enum musb_g_ep0_state { - MUSB_EP0_STAGE_SETUP, /* idle, waiting for setup */ + MUSB_EP0_STAGE_IDLE, /* idle, waiting for SETUP */ + MUSB_EP0_STAGE_SETUP, /* received SETUP */ MUSB_EP0_STAGE_TX, /* IN data */ MUSB_EP0_STAGE_RX, /* OUT data */ MUSB_EP0_STAGE_STATUSIN, /* (after OUT data) */ diff --git a/drivers/usb/musb/musb_gadget_ep0.c b/drivers/usb/musb/musb_gadget_ep0.c index 3f5e30ddfa27..40ed50ecedff 100644 --- a/drivers/usb/musb/musb_gadget_ep0.c +++ b/drivers/usb/musb/musb_gadget_ep0.c @@ -4,6 +4,7 @@ * Copyright 2005 Mentor Graphics Corporation * Copyright (C) 2005-2006 by Texas Instruments * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2008-2009 MontaVista Software, Inc. <source@mvista.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -58,7 +59,8 @@ static char *decode_ep0stage(u8 stage) { switch (stage) { - case MUSB_EP0_STAGE_SETUP: return "idle"; + case MUSB_EP0_STAGE_IDLE: return "idle"; + case MUSB_EP0_STAGE_SETUP: return "setup"; case MUSB_EP0_STAGE_TX: return "in"; case MUSB_EP0_STAGE_RX: return "out"; case MUSB_EP0_STAGE_ACKWAIT: return "wait"; @@ -628,7 +630,7 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb) musb_writew(regs, MUSB_CSR0, csr & ~MUSB_CSR0_P_SENTSTALL); retval = IRQ_HANDLED; - musb->ep0_state = MUSB_EP0_STAGE_SETUP; + musb->ep0_state = MUSB_EP0_STAGE_IDLE; csr = musb_readw(regs, MUSB_CSR0); } @@ -636,7 +638,18 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb) if (csr & MUSB_CSR0_P_SETUPEND) { musb_writew(regs, MUSB_CSR0, MUSB_CSR0_P_SVDSETUPEND); retval = IRQ_HANDLED; - musb->ep0_state = MUSB_EP0_STAGE_SETUP; + /* Transition into the early status phase */ + switch (musb->ep0_state) { + case MUSB_EP0_STAGE_TX: + musb->ep0_state = MUSB_EP0_STAGE_STATUSOUT; + break; + case MUSB_EP0_STAGE_RX: + musb->ep0_state = MUSB_EP0_STAGE_STATUSIN; + break; + default: + ERR("SetupEnd came in a wrong ep0stage %s", + decode_ep0stage(musb->ep0_state)); + } csr = musb_readw(regs, MUSB_CSR0); /* NOTE: request may need completion */ } @@ -697,11 +710,31 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb) if (req) musb_g_ep0_giveback(musb, req); } + + /* + * In case when several interrupts can get coalesced, + * check to see if we've already received a SETUP packet... + */ + if (csr & MUSB_CSR0_RXPKTRDY) + goto setup; + + retval = IRQ_HANDLED; + musb->ep0_state = MUSB_EP0_STAGE_IDLE; + break; + + case MUSB_EP0_STAGE_IDLE: + /* + * This state is typically (but not always) indiscernible + * from the status states since the corresponding interrupts + * tend to happen within too little period of time (with only + * a zero-length packet in between) and so get coalesced... + */ retval = IRQ_HANDLED; musb->ep0_state = MUSB_EP0_STAGE_SETUP; /* FALLTHROUGH */ case MUSB_EP0_STAGE_SETUP: +setup: if (csr & MUSB_CSR0_RXPKTRDY) { struct usb_ctrlrequest setup; int handled = 0; @@ -783,7 +816,7 @@ irqreturn_t musb_g_ep0_irq(struct musb *musb) stall: DBG(3, "stall (%d)\n", handled); musb->ackpend |= MUSB_CSR0_P_SENDSTALL; - musb->ep0_state = MUSB_EP0_STAGE_SETUP; + musb->ep0_state = MUSB_EP0_STAGE_IDLE; finish: musb_writew(regs, MUSB_CSR0, musb->ackpend); @@ -803,7 +836,7 @@ finish: /* "can't happen" */ WARN_ON(1); musb_writew(regs, MUSB_CSR0, MUSB_CSR0_P_SENDSTALL); - musb->ep0_state = MUSB_EP0_STAGE_SETUP; + musb->ep0_state = MUSB_EP0_STAGE_IDLE; break; } @@ -959,7 +992,7 @@ static int musb_g_ep0_halt(struct usb_ep *e, int value) csr |= MUSB_CSR0_P_SENDSTALL; musb_writew(regs, MUSB_CSR0, csr); - musb->ep0_state = MUSB_EP0_STAGE_SETUP; + musb->ep0_state = MUSB_EP0_STAGE_IDLE; musb->ackpend = 0; break; default: -- cgit v1.2.3 From 37e3ee991091e08d4902e3d2694c723446a89a8d Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov <sshtylyov@ru.mvista.com> Date: Fri, 27 Mar 2009 12:53:32 -0700 Subject: musb_gadget: suppress "parasitic" TX interrupts with CPPI Suppress "parasitic" endpoint interrupts in the DMA mode when using CPPI DMA driver; they're caused by the MUSB gadget driver using the DMA request mode 0 instead of the mode 1. Signed-off-by: Sergei Shtylyov <sshtylyov@ru.mvista.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/musb_gadget.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index f79440cdfe7e..bc197b28ac40 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -349,7 +349,8 @@ static void txstate(struct musb *musb, struct musb_request *req) #elif defined(CONFIG_USB_TI_CPPI_DMA) /* program endpoint CSR first, then setup DMA */ csr &= ~(MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_TXPKTRDY); - csr |= MUSB_TXCSR_MODE | MUSB_TXCSR_DMAENAB; + csr |= MUSB_TXCSR_DMAENAB | MUSB_TXCSR_DMAMODE | + MUSB_TXCSR_MODE; musb_writew(epio, MUSB_TXCSR, (MUSB_TXCSR_P_WZC_BITS & ~MUSB_TXCSR_P_UNDERRUN) | csr); -- cgit v1.2.3 From 846099a61cf549f450178f1fb3e27adcbd9dcfc2 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov <sshtylyov@ru.mvista.com> Date: Fri, 27 Mar 2009 12:54:21 -0700 Subject: musb_host: refactor musb_save_toggle() (take 2) Refactor musb_save_toggle() as follows: - replace 'struct musb_hw_ep *ep' parameter by 'struct musb_qh *qh' to avoid re-calculating this value - move usb_settogle() call out of the *if* operator. This is a net minor shrink of source and object code. Signed-off-by: Sergei Shtylyov <sshtylyov@ru.mvista.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/musb_host.c | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index db1b57415ec7..bf194d244533 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -321,35 +321,24 @@ __acquires(musb->lock) spin_lock(&musb->lock); } -/* for bulk/interrupt endpoints only */ -static inline void -musb_save_toggle(struct musb_hw_ep *ep, int is_in, struct urb *urb) +/* For bulk/interrupt endpoints only */ +static inline void musb_save_toggle(struct musb_qh *qh, int is_in, + struct urb *urb) { - struct usb_device *udev = urb->dev; + void __iomem *epio = qh->hw_ep->regs; u16 csr; - void __iomem *epio = ep->regs; - struct musb_qh *qh; - /* FIXME: the current Mentor DMA code seems to have + /* + * FIXME: the current Mentor DMA code seems to have * problems getting toggle correct. */ - if (is_in || ep->is_shared_fifo) - qh = ep->in_qh; + if (is_in) + csr = musb_readw(epio, MUSB_RXCSR) & MUSB_RXCSR_H_DATATOGGLE; else - qh = ep->out_qh; + csr = musb_readw(epio, MUSB_TXCSR) & MUSB_TXCSR_H_DATATOGGLE; - if (!is_in) { - csr = musb_readw(epio, MUSB_TXCSR); - usb_settoggle(udev, qh->epnum, 1, - (csr & MUSB_TXCSR_H_DATATOGGLE) - ? 1 : 0); - } else { - csr = musb_readw(epio, MUSB_RXCSR); - usb_settoggle(udev, qh->epnum, 0, - (csr & MUSB_RXCSR_H_DATATOGGLE) - ? 1 : 0); - } + usb_settoggle(urb->dev, qh->epnum, !is_in, csr ? 1 : 0); } /* caller owns controller lock, irqs are blocked */ @@ -365,7 +354,7 @@ musb_giveback(struct musb_qh *qh, struct urb *urb, int status) switch (qh->type) { case USB_ENDPOINT_XFER_BULK: case USB_ENDPOINT_XFER_INT: - musb_save_toggle(ep, is_in, urb); + musb_save_toggle(qh, is_in, urb); break; case USB_ENDPOINT_XFER_ISOC: if (status == 0 && urb->error_count) @@ -1427,7 +1416,7 @@ static void musb_bulk_rx_nak_timeout(struct musb *musb, struct musb_hw_ep *ep) urb->actual_length += dma->actual_len; dma->actual_len = 0L; } - musb_save_toggle(ep, 1, urb); + musb_save_toggle(cur_qh, 1, urb); /* move cur_qh to end of queue */ list_move_tail(&cur_qh->ring, &musb->in_bulk); -- cgit v1.2.3 From 3e5c6dc71146c2c3f21d60d3b4b25dc7755d5339 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov <sshtylyov@ru.mvista.com> Date: Fri, 27 Mar 2009 12:55:16 -0700 Subject: musb_host: factor out musb_ep_{get|set}_qh() Factor out the often used code to get/set the active 'qh' pointer for the hardware endpoint. Change the way the case of a shared FIFO is handled by setting *both* 'in_qh' and 'out_qh' fields of 'struct musb_hw_ep'. That seems more consistent and makes getting to the current 'qh' easy when the code knows the direction beforehand. While at it, turn some assignments into intializers and fix declaration style in the vicinity. Signed-off-by: Sergei Shtylyov <sshtylyov@ru.mvista.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/musb_host.c | 56 +++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 34 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index bf194d244533..17d14f26bd6c 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -181,6 +181,19 @@ static inline void musb_h_tx_dma_start(struct musb_hw_ep *ep) musb_writew(ep->regs, MUSB_TXCSR, txcsr); } +static void musb_ep_set_qh(struct musb_hw_ep *ep, int is_in, struct musb_qh *qh) +{ + if (is_in != 0 || ep->is_shared_fifo) + ep->in_qh = qh; + if (is_in == 0 || ep->is_shared_fifo) + ep->out_qh = qh; +} + +static struct musb_qh *musb_ep_get_qh(struct musb_hw_ep *ep, int is_in) +{ + return is_in ? ep->in_qh : ep->out_qh; +} + /* * Start the URB at the front of an endpoint's queue * end must be claimed from the caller. @@ -210,7 +223,6 @@ musb_start_urb(struct musb *musb, int is_in, struct musb_qh *qh) case USB_ENDPOINT_XFER_CONTROL: /* control transfers always start with SETUP */ is_in = 0; - hw_ep->out_qh = qh; musb->ep0_stage = MUSB_EP0_START; buf = urb->setup_packet; len = 8; @@ -239,10 +251,7 @@ musb_start_urb(struct musb *musb, int is_in, struct musb_qh *qh) epnum, buf + offset, len); /* Configure endpoint */ - if (is_in || hw_ep->is_shared_fifo) - hw_ep->in_qh = qh; - else - hw_ep->out_qh = qh; + musb_ep_set_qh(hw_ep, is_in, qh); musb_ep_program(musb, epnum, urb, !is_in, buf, offset, len); /* transmit may have more work: start it when it is time */ @@ -377,11 +386,8 @@ musb_giveback(struct musb_qh *qh, struct urb *urb, int status) else ep->tx_reinit = 1; - /* clobber old pointers to this qh */ - if (is_in || ep->is_shared_fifo) - ep->in_qh = NULL; - else - ep->out_qh = NULL; + /* Clobber old pointers to this qh */ + musb_ep_set_qh(ep, is_in, NULL); qh->hep->hcpriv = NULL; switch (qh->type) { @@ -424,12 +430,7 @@ static void musb_advance_schedule(struct musb *musb, struct urb *urb, struct musb_hw_ep *hw_ep, int is_in) { - struct musb_qh *qh; - - if (is_in || hw_ep->is_shared_fifo) - qh = hw_ep->in_qh; - else - qh = hw_ep->out_qh; + struct musb_qh *qh = musb_ep_get_qh(hw_ep, is_in); if (urb->status == -EINPROGRESS) qh = musb_giveback(qh, urb, 0); @@ -692,15 +693,8 @@ static void musb_ep_program(struct musb *musb, u8 epnum, void __iomem *mbase = musb->mregs; struct musb_hw_ep *hw_ep = musb->endpoints + epnum; void __iomem *epio = hw_ep->regs; - struct musb_qh *qh; - u16 packet_sz; - - if (!is_out || hw_ep->is_shared_fifo) - qh = hw_ep->in_qh; - else - qh = hw_ep->out_qh; - - packet_sz = qh->maxpacket; + struct musb_qh *qh = musb_ep_get_qh(hw_ep, !is_out); + u16 packet_sz = qh->maxpacket; DBG(3, "%s hw%d urb %p spd%d dev%d ep%d%s " "h_addr%02x h_port%02x bytes %d\n", @@ -1118,17 +1112,14 @@ void musb_host_tx(struct musb *musb, u8 epnum) u16 tx_csr; size_t length = 0; size_t offset = 0; - struct urb *urb; struct musb_hw_ep *hw_ep = musb->endpoints + epnum; void __iomem *epio = hw_ep->regs; - struct musb_qh *qh = hw_ep->is_shared_fifo ? hw_ep->in_qh - : hw_ep->out_qh; + struct musb_qh *qh = hw_ep->out_qh; + struct urb *urb = next_urb(qh); u32 status = 0; void __iomem *mbase = musb->mregs; struct dma_channel *dma; - urb = next_urb(qh); - musb_ep_select(mbase, epnum); tx_csr = musb_readw(epio, MUSB_TXCSR); @@ -1806,10 +1797,7 @@ static int musb_schedule( epnum++, hw_ep++) { int diff; - if (is_in || hw_ep->is_shared_fifo) { - if (hw_ep->in_qh != NULL) - continue; - } else if (hw_ep->out_qh != NULL) + if (musb_ep_get_qh(hw_ep, is_in) != NULL) continue; if (hw_ep == musb->bulk_ep) -- cgit v1.2.3 From 22a0d6f1383c85a7a9759cb805fd06c848c9c4d3 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov <sshtylyov@ru.mvista.com> Date: Fri, 27 Mar 2009 12:56:26 -0700 Subject: musb_host: simplify check for active URB The existance of the scheduling list shouldn't matter in determining whether there's currectly an URB executing on a hardware endpoint. What should actually matter is the 'in_qh' or 'out_qh' fields of the 'struct musb_hw_ep' -- those are set in musb_start_urb() and cleared in musb_giveback() when the endpoint's URB list drains. Hence we should be able to replace the big *switch* statements in musb_urb_dequeue() and musb_h_disable() with mere musb_ep_get_qh() calls... While at it, do some more changes: - add 'is_in' variable to musb_urb_dequeue(); - remove the unnecessary 'epnum' variable from musb_h_disable(); - fix the comment style in the vicinity. This is a minor shrink of source and object code. Signed-off-by: Sergei Shtylyov <sshtylyov@ru.mvista.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/musb_host.c | 72 +++++++++----------------------------------- 1 file changed, 14 insertions(+), 58 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index 17d14f26bd6c..e0dacbb336d0 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -2089,14 +2089,14 @@ static int musb_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) { struct musb *musb = hcd_to_musb(hcd); struct musb_qh *qh; - struct list_head *sched; unsigned long flags; + int is_in = usb_pipein(urb->pipe); int ret; DBG(4, "urb=%p, dev%d ep%d%s\n", urb, usb_pipedevice(urb->pipe), usb_pipeendpoint(urb->pipe), - usb_pipein(urb->pipe) ? "in" : "out"); + is_in ? "in" : "out"); spin_lock_irqsave(&musb->lock, flags); ret = usb_hcd_check_unlink_urb(hcd, urb, status); @@ -2107,45 +2107,23 @@ static int musb_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) if (!qh) goto done; - /* Any URB not actively programmed into endpoint hardware can be + /* + * Any URB not actively programmed into endpoint hardware can be * immediately given back; that's any URB not at the head of an * endpoint queue, unless someday we get real DMA queues. And even * if it's at the head, it might not be known to the hardware... * - * Otherwise abort current transfer, pending dma, etc.; urb->status + * Otherwise abort current transfer, pending DMA, etc.; urb->status * has already been updated. This is a synchronous abort; it'd be * OK to hold off until after some IRQ, though. + * + * NOTE: qh is invalid unless !list_empty(&hep->urb_list) */ - if (!qh->is_ready || urb->urb_list.prev != &qh->hep->urb_list) - ret = -EINPROGRESS; - else { - switch (qh->type) { - case USB_ENDPOINT_XFER_CONTROL: - sched = &musb->control; - break; - case USB_ENDPOINT_XFER_BULK: - if (qh->mux == 1) { - if (usb_pipein(urb->pipe)) - sched = &musb->in_bulk; - else - sched = &musb->out_bulk; - break; - } - default: - /* REVISIT when we get a schedule tree, periodic - * transfers won't always be at the head of a - * singleton queue... - */ - sched = NULL; - break; - } - } - - /* NOTE: qh is invalid unless !list_empty(&hep->urb_list) */ - if (ret < 0 || (sched && qh != first_qh(sched))) { + if (!qh->is_ready + || urb->urb_list.prev != &qh->hep->urb_list + || musb_ep_get_qh(qh->hw_ep, is_in) != qh) { int ready = qh->is_ready; - ret = 0; qh->is_ready = 0; __musb_giveback(musb, urb, 0); qh->is_ready = ready; @@ -2169,13 +2147,11 @@ done: static void musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep) { - u8 epnum = hep->desc.bEndpointAddress; + u8 is_in = hep->desc.bEndpointAddress & USB_DIR_IN; unsigned long flags; struct musb *musb = hcd_to_musb(hcd); - u8 is_in = epnum & USB_DIR_IN; struct musb_qh *qh; struct urb *urb; - struct list_head *sched; spin_lock_irqsave(&musb->lock, flags); @@ -2183,31 +2159,11 @@ musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep) if (qh == NULL) goto exit; - switch (qh->type) { - case USB_ENDPOINT_XFER_CONTROL: - sched = &musb->control; - break; - case USB_ENDPOINT_XFER_BULK: - if (qh->mux == 1) { - if (is_in) - sched = &musb->in_bulk; - else - sched = &musb->out_bulk; - break; - } - default: - /* REVISIT when we get a schedule tree, periodic transfers - * won't always be at the head of a singleton queue... - */ - sched = NULL; - break; - } - - /* NOTE: qh is invalid unless !list_empty(&hep->urb_list) */ + /* NOTE: qh is invalid unless !list_empty(&hep->urb_list) */ - /* kick first urb off the hardware, if needed */ + /* Kick the first URB off the hardware, if needed */ qh->is_ready = 0; - if (!sched || qh == first_qh(sched)) { + if (musb_ep_get_qh(qh->hw_ep, is_in) == qh) { urb = next_urb(qh); /* make software (then hardware) stop ASAP */ -- cgit v1.2.3 From 81ec4e4a5116c2bccec2dd1d350ceb4372846ba8 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov <sshtylyov@ru.mvista.com> Date: Fri, 27 Mar 2009 12:57:50 -0700 Subject: musb_host: streamline musb_cleanup_urb() calls The argument for the 'is_in' parameter of musb_cleanup_urb() is always extracted from an URB that's passed to the function. So that parameter is superfluous; remove it. Signed-off-by: Sergei Shtylyov <sshtylyov@ru.mvista.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/musb_host.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index e0dacbb336d0..e666f609ad7e 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -2029,14 +2029,15 @@ done: * called with controller locked, irqs blocked * that hardware queue advances to the next transfer, unless prevented */ -static int musb_cleanup_urb(struct urb *urb, struct musb_qh *qh, int is_in) +static int musb_cleanup_urb(struct urb *urb, struct musb_qh *qh) { struct musb_hw_ep *ep = qh->hw_ep; void __iomem *epio = ep->regs; unsigned hw_end = ep->epnum; void __iomem *regs = ep->musb->mregs; - u16 csr; + int is_in = usb_pipein(urb->pipe); int status = 0; + u16 csr; musb_ep_select(regs, hw_end); @@ -2137,7 +2138,7 @@ static int musb_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) kfree(qh); } } else - ret = musb_cleanup_urb(urb, qh, urb->pipe & USB_DIR_IN); + ret = musb_cleanup_urb(urb, qh); done: spin_unlock_irqrestore(&musb->lock, flags); return ret; @@ -2171,7 +2172,7 @@ musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep) urb->status = -ESHUTDOWN; /* cleanup */ - musb_cleanup_urb(urb, qh, urb->pipe & USB_DIR_IN); + musb_cleanup_urb(urb, qh); /* Then nuke all the others ... and advance the * queue on hw_ep (e.g. bulk ring) when we're done. -- cgit v1.2.3 From c9cd06b3d6ea825c62e277def929cc4315802b48 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov <sshtylyov@ru.mvista.com> Date: Fri, 27 Mar 2009 12:58:31 -0700 Subject: musb_host: refactor URB giveback As musb_advance_schedule() is now the only remaning caller of musb_giveback() (and the only valid context of such call), just fold the latter into the former and then rename __musb_giveback() into musb_giveback(). This is a net minor shrink. Signed-off-by: Sergei Shtylyov <sshtylyov@ru.mvista.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/musb_host.c | 54 ++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 34 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index e666f609ad7e..c1bb192ecbef 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -295,9 +295,8 @@ start: } } -/* caller owns controller lock, irqs are blocked */ -static void -__musb_giveback(struct musb *musb, struct urb *urb, int status) +/* Context: caller owns controller lock, IRQs are blocked */ +static void musb_giveback(struct musb *musb, struct urb *urb, int status) __releases(musb->lock) __acquires(musb->lock) { @@ -350,14 +349,22 @@ static inline void musb_save_toggle(struct musb_qh *qh, int is_in, usb_settoggle(urb->dev, qh->epnum, !is_in, csr ? 1 : 0); } -/* caller owns controller lock, irqs are blocked */ -static struct musb_qh * -musb_giveback(struct musb_qh *qh, struct urb *urb, int status) +/* + * Advance this hardware endpoint's queue, completing the specified URB and + * advancing to either the next URB queued to that qh, or else invalidating + * that qh and advancing to the next qh scheduled after the current one. + * + * Context: caller owns controller lock, IRQs are blocked + */ +static void musb_advance_schedule(struct musb *musb, struct urb *urb, + struct musb_hw_ep *hw_ep, int is_in) { + struct musb_qh *qh = musb_ep_get_qh(hw_ep, is_in); struct musb_hw_ep *ep = qh->hw_ep; - struct musb *musb = ep->musb; - int is_in = usb_pipein(urb->pipe); int ready = qh->is_ready; + int status; + + status = (urb->status == -EINPROGRESS) ? 0 : urb->status; /* save toggle eagerly, for paranoia */ switch (qh->type) { @@ -366,13 +373,13 @@ musb_giveback(struct musb_qh *qh, struct urb *urb, int status) musb_save_toggle(qh, is_in, urb); break; case USB_ENDPOINT_XFER_ISOC: - if (status == 0 && urb->error_count) + if (urb->error_count) status = -EXDEV; break; } qh->is_ready = 0; - __musb_giveback(musb, urb, status); + musb_giveback(musb, urb, status); qh->is_ready = ready; /* reclaim resources (and bandwidth) ASAP; deschedule it, and @@ -416,31 +423,10 @@ musb_giveback(struct musb_qh *qh, struct urb *urb, int status) break; } } - return qh; -} - -/* - * Advance this hardware endpoint's queue, completing the specified urb and - * advancing to either the next urb queued to that qh, or else invalidating - * that qh and advancing to the next qh scheduled after the current one. - * - * Context: caller owns controller lock, irqs are blocked - */ -static void -musb_advance_schedule(struct musb *musb, struct urb *urb, - struct musb_hw_ep *hw_ep, int is_in) -{ - struct musb_qh *qh = musb_ep_get_qh(hw_ep, is_in); - - if (urb->status == -EINPROGRESS) - qh = musb_giveback(qh, urb, 0); - else - qh = musb_giveback(qh, urb, urb->status); if (qh != NULL && qh->is_ready) { DBG(4, "... next ep%d %cX urb %p\n", - hw_ep->epnum, is_in ? 'R' : 'T', - next_urb(qh)); + hw_ep->epnum, is_in ? 'R' : 'T', next_urb(qh)); musb_start_urb(musb, is_in, qh); } } @@ -2126,7 +2112,7 @@ static int musb_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) int ready = qh->is_ready; qh->is_ready = 0; - __musb_giveback(musb, urb, 0); + musb_giveback(musb, urb, 0); qh->is_ready = ready; /* If nothing else (usually musb_giveback) is using it @@ -2188,7 +2174,7 @@ musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep) * will activate any of these as it advances. */ while (!list_empty(&hep->urb_list)) - __musb_giveback(musb, next_urb(qh), -ESHUTDOWN); + musb_giveback(musb, next_urb(qh), -ESHUTDOWN); hep->hcpriv = NULL; list_del(&qh->ring); -- cgit v1.2.3 From 91e9c4fec7ee777213859aa1a18bf0b885527637 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov <sshtylyov@ru.mvista.com> Date: Fri, 27 Mar 2009 12:59:46 -0700 Subject: musb: split out CPPI interrupt handler As DaVinci DM646x has a dedicated CPPI DMA interrupt, replace cppi_completion() (which has always been kind of layering violation) by a complete CPPI interrupt handler. [ dbrownell@users.sourceforge.net: only cppi_dma.c needs platform device header, not cppi_dma.h ] Signed-off-by: Dmitry Krivoschekov <dkrivoschekov@ru.mvista.com> Signed-off-by: Sergei Shtylyov <sshtylyov@ru.mvista.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/cppi_dma.c | 34 +++++++++++++++++++++++++++++++--- drivers/usb/musb/cppi_dma.h | 6 ++++-- drivers/usb/musb/davinci.c | 14 ++++---------- 3 files changed, 39 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/cppi_dma.c b/drivers/usb/musb/cppi_dma.c index 1976e9b41800..c3577bbbae6c 100644 --- a/drivers/usb/musb/cppi_dma.c +++ b/drivers/usb/musb/cppi_dma.c @@ -6,6 +6,7 @@ * The TUSB6020, using VLYNQ, has CPPI that looks much like DaVinci. */ +#include <linux/platform_device.h> #include <linux/usb.h> #include "musb_core.h" @@ -1145,17 +1146,27 @@ static bool cppi_rx_scan(struct cppi *cppi, unsigned ch) return completed; } -void cppi_completion(struct musb *musb, u32 rx, u32 tx) +irqreturn_t cppi_interrupt(int irq, void *dev_id) { - void __iomem *tibase; - int i, index; + struct musb *musb = dev_id; struct cppi *cppi; + void __iomem *tibase; struct musb_hw_ep *hw_ep = NULL; + u32 rx, tx; + int i, index; cppi = container_of(musb->dma_controller, struct cppi, controller); tibase = musb->ctrl_base; + tx = musb_readl(tibase, DAVINCI_TXCPPI_MASKED_REG); + rx = musb_readl(tibase, DAVINCI_RXCPPI_MASKED_REG); + + if (!tx && !rx) + return IRQ_NONE; + + DBG(4, "CPPI IRQ Tx%x Rx%x\n", tx, rx); + /* process TX channels */ for (index = 0; tx; tx = tx >> 1, index++) { struct cppi_channel *tx_ch; @@ -1273,6 +1284,8 @@ void cppi_completion(struct musb *musb, u32 rx, u32 tx) /* write to CPPI EOI register to re-enable interrupts */ musb_writel(tibase, DAVINCI_CPPI_EOI_REG, 0); + + return IRQ_HANDLED; } /* Instantiate a software object representing a DMA controller. */ @@ -1280,6 +1293,9 @@ struct dma_controller *__init dma_controller_create(struct musb *musb, void __iomem *mregs) { struct cppi *controller; + struct device *dev = musb->controller; + struct platform_device *pdev = to_platform_device(dev); + int irq = platform_get_irq(pdev, 1); controller = kzalloc(sizeof *controller, GFP_KERNEL); if (!controller) @@ -1310,6 +1326,15 @@ dma_controller_create(struct musb *musb, void __iomem *mregs) return NULL; } + if (irq > 0) { + if (request_irq(irq, cppi_interrupt, 0, "cppi-dma", musb)) { + dev_err(dev, "request_irq %d failed!\n", irq); + dma_controller_destroy(&controller->controller); + return NULL; + } + controller->irq = irq; + } + return &controller->controller; } @@ -1322,6 +1347,9 @@ void dma_controller_destroy(struct dma_controller *c) cppi = container_of(c, struct cppi, controller); + if (cppi->irq) + free_irq(cppi->irq, cppi->musb); + /* assert: caller stopped the controller first */ dma_pool_destroy(cppi->pool); diff --git a/drivers/usb/musb/cppi_dma.h b/drivers/usb/musb/cppi_dma.h index 729b4071787b..8a39de3e6e47 100644 --- a/drivers/usb/musb/cppi_dma.h +++ b/drivers/usb/musb/cppi_dma.h @@ -119,6 +119,8 @@ struct cppi { void __iomem *mregs; /* Mentor regs */ void __iomem *tibase; /* TI/CPPI regs */ + int irq; + struct cppi_channel tx[4]; struct cppi_channel rx[4]; @@ -127,7 +129,7 @@ struct cppi { struct list_head tx_complete; }; -/* irq handling hook */ -extern void cppi_completion(struct musb *, u32 rx, u32 tx); +/* CPPI IRQ handler */ +extern irqreturn_t cppi_interrupt(int, void *); #endif /* end of ifndef _CPPI_DMA_H_ */ diff --git a/drivers/usb/musb/davinci.c b/drivers/usb/musb/davinci.c index 898b52fcff5d..6e14e06ff820 100644 --- a/drivers/usb/musb/davinci.c +++ b/drivers/usb/musb/davinci.c @@ -265,6 +265,7 @@ static irqreturn_t davinci_interrupt(int irq, void *__hci) irqreturn_t retval = IRQ_NONE; struct musb *musb = __hci; void __iomem *tibase = musb->ctrl_base; + struct cppi *cppi; u32 tmp; spin_lock_irqsave(&musb->lock, flags); @@ -281,16 +282,9 @@ static irqreturn_t davinci_interrupt(int irq, void *__hci) /* CPPI interrupts share the same IRQ line, but have their own * mask, state, "vector", and EOI registers. */ - if (is_cppi_enabled()) { - u32 cppi_tx = musb_readl(tibase, DAVINCI_TXCPPI_MASKED_REG); - u32 cppi_rx = musb_readl(tibase, DAVINCI_RXCPPI_MASKED_REG); - - if (cppi_tx || cppi_rx) { - DBG(4, "CPPI IRQ t%x r%x\n", cppi_tx, cppi_rx); - cppi_completion(musb, cppi_rx, cppi_tx); - retval = IRQ_HANDLED; - } - } + cppi = container_of(musb->dma_controller, struct cppi, controller); + if (is_cppi_enabled() && musb->dma_controller && !cppi->irq) + retval = cppi_interrupt(irq, __hci); /* ack and handle non-CPPI interrupts */ tmp = musb_readl(tibase, DAVINCI_USB_INT_SRC_MASKED_REG); -- cgit v1.2.3 From d8b175e78b9debdacd31fa74da5dedd6a6285f94 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Date: Sat, 28 Mar 2009 00:27:13 +0100 Subject: USB: move twl4030_usb's probe function to .devinit.text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A pointer to twl4030_usb_probe is passed to the core via platform_driver_register and so the function must not disappear when the .init sections are discarded. Otherwise (if also having HOTPLUG=y) unbinding and binding a device to the driver via sysfs will result in an oops as does a device being registered late. An alternative to this patch is using platform_driver_probe instead of platform_driver_register plus removing the pointer to the probe function from the struct platform_driver. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Cc: Jouni Hogander <jouni.hogander@nokia.com> Cc: Kalle Jokiniemi <kalle.jokiniemi@digia.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: David Brownell <dbrownell@users.sourceforge.net> Cc: Tony Lindgren <tony@atomide.com> Cc: Kevin Hilman <khilman@deeprootsystems.com> Cc: Felipe Balbi <felipe.balbi@nokia.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/otg/twl4030-usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/otg/twl4030-usb.c b/drivers/usb/otg/twl4030-usb.c index d9478d0e1c8b..c34e63910acb 100644 --- a/drivers/usb/otg/twl4030-usb.c +++ b/drivers/usb/otg/twl4030-usb.c @@ -641,7 +641,7 @@ static int twl4030_set_host(struct otg_transceiver *x, struct usb_bus *host) return 0; } -static int __init twl4030_usb_probe(struct platform_device *pdev) +static int __devinit twl4030_usb_probe(struct platform_device *pdev) { struct twl4030_usb_data *pdata = pdev->dev.platform_data; struct twl4030_usb *twl; -- cgit v1.2.3 From 8864bd8606508f6cb66bb0668250cc0672060b65 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Date: Sat, 28 Mar 2009 00:27:00 +0100 Subject: USB: move r8a66597_hcd's probe function to .devinit.text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A pointer to r8a66597_probe is passed to the core via platform_driver_register and so the function must not disappear when the .init sections are discarded. Otherwise (if also having HOTPLUG=y) unbinding and binding a device to the driver via sysfs will result in an oops as does a device being registered late. An alternative to this patch is using platform_driver_probe instead of platform_driver_register plus removing the pointer to the probe function from the struct platform_driver. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Acked-by: Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> Cc: Magnus Damm <damm@igel.co.jp> Cc: Stephen Rothwell <sfr@canb.auug.org.au> Cc: Paul Mundt <lethal@linux-sh.org> Cc: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/r8a66597-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index f1626e58c141..3e1216ad86bd 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -2373,7 +2373,7 @@ static int __init_or_module r8a66597_remove(struct platform_device *pdev) return 0; } -static int __init r8a66597_probe(struct platform_device *pdev) +static int __devinit r8a66597_probe(struct platform_device *pdev) { #if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK) char clk_name[8]; -- cgit v1.2.3 From dc2f2b7505c195a6963fc07b549e269eee417261 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Date: Sat, 28 Mar 2009 00:26:33 +0100 Subject: USB: move orion-ehci's probe function to .devinit.text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A pointer to ehci_orion_drv_probe is passed to the core via platform_driver_register and so the function must not disappear when the .init sections are discarded. Otherwise (if also having HOTPLUG=y) unbinding and binding a device to the driver via sysfs will result in an oops as does a device being registered late. An alternative to this patch is using platform_driver_probe instead of platform_driver_register plus removing the pointer to the probe function from the struct platform_driver. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Cc: Ronen Shitrit <rshitrit@marvell.com> Cc: Lennert Buytenhek <buytenh@marvell.com> Cc: Alan Stern <stern@rowland.harvard.edu> Cc: David Brownell <david-b@pacbell.net> Cc: Nicolas Pitre <nico@marvell.com> Cc: Russell King <rmk+kernel@arm.linux.org.uk> Cc: Tzachi Perelstein <tzachi@marvell.com> Cc: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-orion.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c index 9d487908012e..17dc15407a07 100644 --- a/drivers/usb/host/ehci-orion.c +++ b/drivers/usb/host/ehci-orion.c @@ -187,7 +187,7 @@ ehci_orion_conf_mbus_windows(struct usb_hcd *hcd, } } -static int __init ehci_orion_drv_probe(struct platform_device *pdev) +static int __devinit ehci_orion_drv_probe(struct platform_device *pdev) { struct orion_ehci_data *pd = pdev->dev.platform_data; struct resource *res; -- cgit v1.2.3 From def6f8b978618d50daaddb92331d398da9e141f1 Mon Sep 17 00:00:00 2001 From: David Brownell <dbrownell@users.sourceforge.net> Date: Tue, 31 Mar 2009 12:26:10 -0700 Subject: USB: twl4030-usb: fix minor reporting goofage Fix a reporting glitch in the twl4030 USB transceiver code. It wasn't properly distinguishing the two types of active USB link: ID grounded, vs not. In the current code that distinction doesn't much matter; in the future this bugfix should help support better USB controller communications. Provide a comment sorting out some of the cryptic bits of the manual: different sections use different names for key signals, and the register definitions don't help much without the explanations and diagrams. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/otg/twl4030-usb.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/otg/twl4030-usb.c b/drivers/usb/otg/twl4030-usb.c index c34e63910acb..9e3e7a5c258b 100644 --- a/drivers/usb/otg/twl4030-usb.c +++ b/drivers/usb/otg/twl4030-usb.c @@ -217,6 +217,7 @@ /* In module TWL4030_MODULE_PM_MASTER */ #define PROTECT_KEY 0x0E +#define STS_HW_CONDITIONS 0x0F /* In module TWL4030_MODULE_PM_RECEIVER */ #define VUSB_DEDICATED1 0x7D @@ -351,15 +352,26 @@ static enum linkstat twl4030_usb_linkstat(struct twl4030_usb *twl) int status; int linkstat = USB_LINK_UNKNOWN; - /* STS_HW_CONDITIONS */ - status = twl4030_readb(twl, TWL4030_MODULE_PM_MASTER, 0x0f); + /* + * For ID/VBUS sensing, see manual section 15.4.8 ... + * except when using only battery backup power, two + * comparators produce VBUS_PRES and ID_PRES signals, + * which don't match docs elsewhere. But ... BIT(7) + * and BIT(2) of STS_HW_CONDITIONS, respectively, do + * seem to match up. If either is true the USB_PRES + * signal is active, the OTG module is activated, and + * its interrupt may be raised (may wake the system). + */ + status = twl4030_readb(twl, TWL4030_MODULE_PM_MASTER, + STS_HW_CONDITIONS); if (status < 0) dev_err(twl->dev, "USB link status err %d\n", status); - else if (status & BIT(7)) - linkstat = USB_LINK_VBUS; - else if (status & BIT(2)) - linkstat = USB_LINK_ID; - else + else if (status & (BIT(7) | BIT(2))) { + if (status & BIT(2)) + linkstat = USB_LINK_ID; + else + linkstat = USB_LINK_VBUS; + } else linkstat = USB_LINK_NONE; dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", -- cgit v1.2.3 From cc835e321a9f3fa5e083436872e198095f4805b9 Mon Sep 17 00:00:00 2001 From: David Brownell <dbrownell@users.sourceforge.net> Date: Tue, 31 Mar 2009 12:28:31 -0700 Subject: USB: nop-usb-xceiv: behave when linked as a module The NOP OTG transceiver driver needs to be usable from modules. Make sure its symbols are always accessible at both compile and link time, and make sure the device instance is allocated from the heap so that device lifetime rules are obeyed. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/otg/nop-usb-xceiv.c | 25 ++++++++++--------------- include/linux/usb/otg.h | 4 ++-- 2 files changed, 12 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/otg/nop-usb-xceiv.c b/drivers/usb/otg/nop-usb-xceiv.c index c567168f89af..9ed5ea568679 100644 --- a/drivers/usb/otg/nop-usb-xceiv.c +++ b/drivers/usb/otg/nop-usb-xceiv.c @@ -22,8 +22,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Current status: - * this is to add "nop" transceiver for all those phy which is - * autonomous such as isp1504 etc. + * This provides a "nop" transceiver for PHYs which are + * autonomous such as isp1504, isp1707, etc. */ #include <linux/module.h> @@ -36,30 +36,25 @@ struct nop_usb_xceiv { struct device *dev; }; -static u64 nop_xceiv_dmamask = DMA_BIT_MASK(32); - -static struct platform_device nop_xceiv_device = { - .name = "nop_usb_xceiv", - .id = -1, - .dev = { - .dma_mask = &nop_xceiv_dmamask, - .coherent_dma_mask = DMA_BIT_MASK(32), - .platform_data = NULL, - }, -}; +static struct platform_device *pd; void usb_nop_xceiv_register(void) { - if (platform_device_register(&nop_xceiv_device) < 0) { + if (pd) + return; + pd = platform_device_register_simple("nop_usb_xceiv", -1, NULL, 0); + if (!pd) { printk(KERN_ERR "Unable to register usb nop transceiver\n"); return; } } +EXPORT_SYMBOL(usb_nop_xceiv_register); void usb_nop_xceiv_unregister(void) { - platform_device_unregister(&nop_xceiv_device); + platform_device_unregister(pd); } +EXPORT_SYMBOL(usb_nop_xceiv_unregister); static inline struct nop_usb_xceiv *xceiv_to_nop(struct otg_transceiver *x) { diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h index 1aaa826396a1..2443c0e7a80c 100644 --- a/include/linux/usb/otg.h +++ b/include/linux/usb/otg.h @@ -80,10 +80,10 @@ struct otg_transceiver { /* for board-specific init logic */ extern int otg_set_transceiver(struct otg_transceiver *); -#ifdef CONFIG_NOP_USB_XCEIV + +/* sometimes transceivers are accessed only through e.g. ULPI */ extern void usb_nop_xceiv_register(void); extern void usb_nop_xceiv_unregister(void); -#endif /* for usb host and peripheral controller drivers */ -- cgit v1.2.3 From 84e250ffa76dddc1bad84e04248a27f442c25986 Mon Sep 17 00:00:00 2001 From: David Brownell <dbrownell@users.sourceforge.net> Date: Tue, 31 Mar 2009 12:30:04 -0700 Subject: musb: proper hookup to transceiver drivers Let the otg_transceiver in MUSB be managed by an external driver; don't assume it's integrated. OMAP3 chips need it to be external, and there may be ways to interact with the transceiver which add functionality to the system. Platform init code is responsible for setting up the transeciver, probably using the NOP transceiver for integrated transceivers. External ones will use whatever the board init code provided, such as twl4030 or something more hands-off. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/Kconfig | 2 + drivers/usb/musb/blackfin.c | 11 +++-- drivers/usb/musb/davinci.c | 33 +++++++++----- drivers/usb/musb/musb_core.c | 96 ++++++++++++++++++++++------------------- drivers/usb/musb/musb_core.h | 2 +- drivers/usb/musb/musb_gadget.c | 38 ++++++++-------- drivers/usb/musb/musb_host.c | 2 +- drivers/usb/musb/musb_virthub.c | 20 ++++----- drivers/usb/musb/omap2430.c | 62 ++++++++++---------------- drivers/usb/musb/tusb6010.c | 70 +++++++++++++++++++----------- 10 files changed, 181 insertions(+), 155 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index b66e8544d8b9..70073b157f0a 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -10,6 +10,7 @@ comment "Enable Host or Gadget support to see Inventra options" config USB_MUSB_HDRC depends on (USB || USB_GADGET) && HAVE_CLK depends on !SUPERH + select NOP_USB_XCEIV if ARCH_DAVINCI select TWL4030_USB if MACH_OMAP_3430SDP select USB_OTG_UTILS tristate 'Inventra Highspeed Dual Role Controller (TI, ADI, ...)' @@ -55,6 +56,7 @@ comment "Blackfin high speed USB Support" config USB_TUSB6010 boolean "TUSB 6010 support" depends on USB_MUSB_HDRC && !USB_MUSB_SOC + select NOP_USB_XCEIV default y help The TUSB 6010 chip, from Texas Instruments, connects a discrete diff --git a/drivers/usb/musb/blackfin.c b/drivers/usb/musb/blackfin.c index 786134852092..f2f66ebc7362 100644 --- a/drivers/usb/musb/blackfin.c +++ b/drivers/usb/musb/blackfin.c @@ -143,7 +143,7 @@ static void musb_conn_timer_handler(unsigned long _musb) u16 val; spin_lock_irqsave(&musb->lock, flags); - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_IDLE: case OTG_STATE_A_WAIT_BCON: /* Start a new session */ @@ -154,7 +154,7 @@ static void musb_conn_timer_handler(unsigned long _musb) val = musb_readw(musb->mregs, MUSB_DEVCTL); if (!(val & MUSB_DEVCTL_BDEVICE)) { gpio_set_value(musb->config->gpio_vrsel, 1); - musb->xceiv.state = OTG_STATE_A_WAIT_BCON; + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; } else { gpio_set_value(musb->config->gpio_vrsel, 0); @@ -247,6 +247,11 @@ int __init musb_platform_init(struct musb *musb) } gpio_direction_output(musb->config->gpio_vrsel, 0); + usb_nop_xceiv_register(); + musb->xceiv = otg_get_transceiver(); + if (!musb->xceiv) + return -ENODEV; + if (ANOMALY_05000346) { bfin_write_USB_APHY_CALIB(ANOMALY_05000346_value); SSYNC(); @@ -291,7 +296,7 @@ int __init musb_platform_init(struct musb *musb) musb_conn_timer_handler, (unsigned long) musb); } if (is_peripheral_enabled(musb)) - musb->xceiv.set_power = bfin_set_power; + musb->xceiv->set_power = bfin_set_power; musb->isr = blackfin_interrupt; diff --git a/drivers/usb/musb/davinci.c b/drivers/usb/musb/davinci.c index 6e14e06ff820..180d7daa4099 100644 --- a/drivers/usb/musb/davinci.c +++ b/drivers/usb/musb/davinci.c @@ -215,7 +215,7 @@ static void otg_timer(unsigned long _musb) DBG(7, "poll devctl %02x (%s)\n", devctl, otg_state_string(musb)); spin_lock_irqsave(&musb->lock, flags); - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_WAIT_VFALL: /* Wait till VBUS falls below SessionEnd (~0.2V); the 1.3 RTL * seems to mis-handle session "start" otherwise (or in our @@ -226,7 +226,7 @@ static void otg_timer(unsigned long _musb) mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); break; } - musb->xceiv.state = OTG_STATE_A_WAIT_VRISE; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; musb_writel(musb->ctrl_base, DAVINCI_USB_INT_SET_REG, MUSB_INTR_VBUSERROR << DAVINCI_USB_USBINT_SHIFT); break; @@ -251,7 +251,7 @@ static void otg_timer(unsigned long _musb) if (devctl & MUSB_DEVCTL_BDEVICE) mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); else - musb->xceiv.state = OTG_STATE_A_IDLE; + musb->xceiv->state = OTG_STATE_A_IDLE; break; default: break; @@ -325,21 +325,21 @@ static irqreturn_t davinci_interrupt(int irq, void *__hci) * to stop registering in devctl. */ musb->int_usb &= ~MUSB_INTR_VBUSERROR; - musb->xceiv.state = OTG_STATE_A_WAIT_VFALL; + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); WARNING("VBUS error workaround (delay coming)\n"); } else if (is_host_enabled(musb) && drvvbus) { musb->is_active = 1; MUSB_HST_MODE(musb); - musb->xceiv.default_a = 1; - musb->xceiv.state = OTG_STATE_A_WAIT_VRISE; + musb->xceiv->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; portstate(musb->port1_status |= USB_PORT_STAT_POWER); del_timer(&otg_workaround); } else { musb->is_active = 0; MUSB_DEV_MODE(musb); - musb->xceiv.default_a = 0; - musb->xceiv.state = OTG_STATE_B_IDLE; + musb->xceiv->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; portstate(musb->port1_status &= ~USB_PORT_STAT_POWER); } @@ -361,7 +361,7 @@ static irqreturn_t davinci_interrupt(int irq, void *__hci) /* poll for ID change */ if (is_otg_enabled(musb) - && musb->xceiv.state == OTG_STATE_B_IDLE) + && musb->xceiv->state == OTG_STATE_B_IDLE) mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); spin_unlock_irqrestore(&musb->lock, flags); @@ -380,6 +380,11 @@ int __init musb_platform_init(struct musb *musb) void __iomem *tibase = musb->ctrl_base; u32 revision; + usb_nop_xceiv_register(); + musb->xceiv = otg_get_transceiver(); + if (!musb->xceiv) + return -ENODEV; + musb->mregs += DAVINCI_BASE_OFFSET; clk_enable(musb->clock); @@ -387,7 +392,7 @@ int __init musb_platform_init(struct musb *musb) /* returns zero if e.g. not clocked */ revision = musb_readl(tibase, DAVINCI_USB_VERSION_REG); if (revision == 0) - return -ENODEV; + goto fail; if (is_host_enabled(musb)) setup_timer(&otg_workaround, otg_timer, (unsigned long) musb); @@ -421,6 +426,10 @@ int __init musb_platform_init(struct musb *musb) musb->isr = davinci_interrupt; return 0; + +fail: + usb_nop_xceiv_unregister(); + return -ENODEV; } int musb_platform_exit(struct musb *musb) @@ -431,7 +440,7 @@ int musb_platform_exit(struct musb *musb) davinci_source_power(musb, 0 /*off*/, 1); /* delay, to avoid problems with module reload */ - if (is_host_enabled(musb) && musb->xceiv.default_a) { + if (is_host_enabled(musb) && musb->xceiv->default_a) { int maxdelay = 30; u8 devctl, warn = 0; @@ -460,5 +469,7 @@ int musb_platform_exit(struct musb *musb) clk_disable(musb->clock); + usb_nop_xceiv_unregister(); + return 0; } diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 324459b619f7..2460c3986c96 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -267,7 +267,7 @@ void musb_load_testpacket(struct musb *musb) const char *otg_state_string(struct musb *musb) { - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_IDLE: return "a_idle"; case OTG_STATE_A_WAIT_VRISE: return "a_wait_vrise"; case OTG_STATE_A_WAIT_BCON: return "a_wait_bcon"; @@ -302,11 +302,11 @@ void musb_otg_timer_func(unsigned long data) unsigned long flags; spin_lock_irqsave(&musb->lock, flags); - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_B_WAIT_ACON: DBG(1, "HNP: b_wait_acon timeout; back to b_peripheral\n"); musb_g_disconnect(musb); - musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; musb->is_active = 0; break; case OTG_STATE_A_WAIT_BCON: @@ -331,20 +331,20 @@ void musb_hnp_stop(struct musb *musb) void __iomem *mbase = musb->mregs; u8 reg; - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_PERIPHERAL: case OTG_STATE_A_WAIT_VFALL: case OTG_STATE_A_WAIT_BCON: DBG(1, "HNP: Switching back to A-host\n"); musb_g_disconnect(musb); - musb->xceiv.state = OTG_STATE_A_IDLE; + musb->xceiv->state = OTG_STATE_A_IDLE; MUSB_HST_MODE(musb); musb->is_active = 0; break; case OTG_STATE_B_HOST: DBG(1, "HNP: Disabling HR\n"); hcd->self.is_b_host = 0; - musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; MUSB_DEV_MODE(musb); reg = musb_readb(mbase, MUSB_POWER); reg |= MUSB_POWER_SUSPENDM; @@ -402,7 +402,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, if (devctl & MUSB_DEVCTL_HM) { #ifdef CONFIG_USB_MUSB_HDRC_HCD - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_SUSPEND: /* remote wakeup? later, GetPortStatus * will stop RESUME signaling @@ -425,12 +425,12 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, musb->rh_timer = jiffies + msecs_to_jiffies(20); - musb->xceiv.state = OTG_STATE_A_HOST; + musb->xceiv->state = OTG_STATE_A_HOST; musb->is_active = 1; usb_hcd_resume_root_hub(musb_to_hcd(musb)); break; case OTG_STATE_B_WAIT_ACON: - musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; musb->is_active = 1; MUSB_DEV_MODE(musb); break; @@ -441,11 +441,11 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, } #endif } else { - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { #ifdef CONFIG_USB_MUSB_HDRC_HCD case OTG_STATE_A_SUSPEND: /* possibly DISCONNECT is upcoming */ - musb->xceiv.state = OTG_STATE_A_HOST; + musb->xceiv->state = OTG_STATE_A_HOST; usb_hcd_resume_root_hub(musb_to_hcd(musb)); break; #endif @@ -490,7 +490,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, */ musb_writeb(mbase, MUSB_DEVCTL, MUSB_DEVCTL_SESSION); musb->ep0_stage = MUSB_EP0_START; - musb->xceiv.state = OTG_STATE_A_IDLE; + musb->xceiv->state = OTG_STATE_A_IDLE; MUSB_HST_MODE(musb); musb_set_vbus(musb, 1); @@ -516,7 +516,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, * REVISIT: do delays from lots of DEBUG_KERNEL checks * make trouble here, keeping VBUS < 4.4V ? */ - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_HOST: /* recovery is dicey once we've gotten past the * initial stages of enumeration, but if VBUS @@ -602,11 +602,11 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, MUSB_HST_MODE(musb); /* indicate new connection to OTG machine */ - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_B_PERIPHERAL: if (int_usb & MUSB_INTR_SUSPEND) { DBG(1, "HNP: SUSPEND+CONNECT, now b_host\n"); - musb->xceiv.state = OTG_STATE_B_HOST; + musb->xceiv->state = OTG_STATE_B_HOST; hcd->self.is_b_host = 1; int_usb &= ~MUSB_INTR_SUSPEND; } else @@ -614,13 +614,13 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, break; case OTG_STATE_B_WAIT_ACON: DBG(1, "HNP: Waiting to switch to b_host state\n"); - musb->xceiv.state = OTG_STATE_B_HOST; + musb->xceiv->state = OTG_STATE_B_HOST; hcd->self.is_b_host = 1; break; default: if ((devctl & MUSB_DEVCTL_VBUS) == (3 << MUSB_DEVCTL_VBUS_SHIFT)) { - musb->xceiv.state = OTG_STATE_A_HOST; + musb->xceiv->state = OTG_STATE_A_HOST; hcd->self.is_b_host = 0; } break; @@ -650,7 +650,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, } } else if (is_peripheral_capable()) { DBG(1, "BUS RESET as %s\n", otg_state_string(musb)); - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { #ifdef CONFIG_USB_OTG case OTG_STATE_A_SUSPEND: /* We need to ignore disconnect on suspend @@ -673,12 +673,12 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, case OTG_STATE_B_WAIT_ACON: DBG(1, "HNP: RESET (%s), to b_peripheral\n", otg_state_string(musb)); - musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; musb_g_reset(musb); break; #endif case OTG_STATE_B_IDLE: - musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; /* FALLTHROUGH */ case OTG_STATE_B_PERIPHERAL: musb_g_reset(musb); @@ -763,7 +763,7 @@ static irqreturn_t musb_stage2_irq(struct musb *musb, u8 int_usb, MUSB_MODE(musb), devctl); handled = IRQ_HANDLED; - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { #ifdef CONFIG_USB_MUSB_HDRC_HCD case OTG_STATE_A_HOST: case OTG_STATE_A_SUSPEND: @@ -805,7 +805,7 @@ static irqreturn_t musb_stage2_irq(struct musb *musb, u8 int_usb, otg_state_string(musb), devctl, power); handled = IRQ_HANDLED; - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { #ifdef CONFIG_USB_MUSB_OTG case OTG_STATE_A_PERIPHERAL: /* @@ -817,10 +817,10 @@ static irqreturn_t musb_stage2_irq(struct musb *musb, u8 int_usb, case OTG_STATE_B_PERIPHERAL: musb_g_suspend(musb); musb->is_active = is_otg_enabled(musb) - && musb->xceiv.gadget->b_hnp_enable; + && musb->xceiv->gadget->b_hnp_enable; if (musb->is_active) { #ifdef CONFIG_USB_MUSB_OTG - musb->xceiv.state = OTG_STATE_B_WAIT_ACON; + musb->xceiv->state = OTG_STATE_B_WAIT_ACON; DBG(1, "HNP: Setting timer for b_ase0_brst\n"); musb_otg_timer.data = (unsigned long)musb; mod_timer(&musb_otg_timer, jiffies @@ -834,9 +834,9 @@ static irqreturn_t musb_stage2_irq(struct musb *musb, u8 int_usb, + msecs_to_jiffies(musb->a_wait_bcon)); break; case OTG_STATE_A_HOST: - musb->xceiv.state = OTG_STATE_A_SUSPEND; + musb->xceiv->state = OTG_STATE_A_SUSPEND; musb->is_active = is_otg_enabled(musb) - && musb->xceiv.host->b_hnp_enable; + && musb->xceiv->host->b_hnp_enable; break; case OTG_STATE_B_HOST: /* Transition to B_PERIPHERAL, see 6.8.2.6 p 44 */ @@ -1682,7 +1682,7 @@ musb_vbus_store(struct device *dev, struct device_attribute *attr, spin_lock_irqsave(&musb->lock, flags); musb->a_wait_bcon = val; - if (musb->xceiv.state == OTG_STATE_A_WAIT_BCON) + if (musb->xceiv->state == OTG_STATE_A_WAIT_BCON) musb->is_active = 0; musb_platform_try_idle(musb, jiffies + msecs_to_jiffies(val)); spin_unlock_irqrestore(&musb->lock, flags); @@ -1743,8 +1743,8 @@ static void musb_irq_work(struct work_struct *data) struct musb *musb = container_of(data, struct musb, irq_work); static int old_state; - if (musb->xceiv.state != old_state) { - old_state = musb->xceiv.state; + if (musb->xceiv->state != old_state) { + old_state = musb->xceiv->state; sysfs_notify(&musb->controller->kobj, NULL, "mode"); } } @@ -1841,7 +1841,7 @@ static void musb_free(struct musb *musb) } #ifdef CONFIG_USB_MUSB_OTG - put_device(musb->xceiv.dev); + put_device(musb->xceiv->dev); #endif #ifdef CONFIG_USB_MUSB_HDRC_HCD @@ -1922,10 +1922,18 @@ bad_config: } } - /* assume vbus is off */ - - /* platform adjusts musb->mregs and musb->isr if needed, - * and activates clocks + /* The musb_platform_init() call: + * - adjusts musb->mregs and musb->isr if needed, + * - may initialize an integrated tranceiver + * - initializes musb->xceiv, usually by otg_get_transceiver() + * - activates clocks. + * - stops powering VBUS + * - assigns musb->board_set_vbus if host mode is enabled + * + * There are various transciever configurations. Blackfin, + * DaVinci, TUSB60x0, and others integrate them. OMAP3 uses + * external/discrete ones in various flavors (twl4030 family, + * isp1504, non-OTG, etc) mostly hooking up through ULPI. */ musb->isr = generic_interrupt; status = musb_platform_init(musb); @@ -1993,17 +2001,17 @@ bad_config: ? "DMA" : "PIO", musb->nIrq); -#ifdef CONFIG_USB_MUSB_HDRC_HCD - /* host side needs more setup, except for no-host modes */ - if (musb->board_mode != MUSB_PERIPHERAL) { + /* host side needs more setup */ + if (is_host_enabled(musb)) { struct usb_hcd *hcd = musb_to_hcd(musb); - if (musb->board_mode == MUSB_OTG) + otg_set_host(musb->xceiv, &hcd->self); + + if (is_otg_enabled(musb)) hcd->self.otg_port = 1; - musb->xceiv.host = &hcd->self; + musb->xceiv->host = &hcd->self; hcd->power_budget = 2 * (plat->power ? : 250); } -#endif /* CONFIG_USB_MUSB_HDRC_HCD */ /* For the host-only role, we can activate right away. * (We expect the ID pin to be forcibly grounded!!) @@ -2011,8 +2019,8 @@ bad_config: */ if (!is_otg_enabled(musb) && is_host_enabled(musb)) { MUSB_HST_MODE(musb); - musb->xceiv.default_a = 1; - musb->xceiv.state = OTG_STATE_A_IDLE; + musb->xceiv->default_a = 1; + musb->xceiv->state = OTG_STATE_A_IDLE; status = usb_add_hcd(musb_to_hcd(musb), -1, 0); if (status) @@ -2027,8 +2035,8 @@ bad_config: } else /* peripheral is enabled */ { MUSB_DEV_MODE(musb); - musb->xceiv.default_a = 0; - musb->xceiv.state = OTG_STATE_B_IDLE; + musb->xceiv->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; status = musb_gadget_setup(musb); if (status) diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index c2a776ee1947..2b49c989f043 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -356,7 +356,7 @@ struct musb { u16 int_rx; u16 int_tx; - struct otg_transceiver xceiv; + struct otg_transceiver *xceiv; int nIrq; unsigned irq_wake:1; diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index bc197b28ac40..8dfad1189da2 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1406,7 +1406,7 @@ static int musb_gadget_wakeup(struct usb_gadget *gadget) spin_lock_irqsave(&musb->lock, flags); - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_B_PERIPHERAL: /* NOTE: OTG state machine doesn't include B_SUSPENDED; * that's part of the standard usb 1.1 state machine, and @@ -1508,9 +1508,9 @@ static int musb_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA) { struct musb *musb = gadget_to_musb(gadget); - if (!musb->xceiv.set_power) + if (!musb->xceiv->set_power) return -EOPNOTSUPP; - return otg_set_power(&musb->xceiv, mA); + return otg_set_power(musb->xceiv, mA); } static int musb_gadget_pullup(struct usb_gadget *gadget, int is_on) @@ -1733,11 +1733,7 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver) spin_lock_irqsave(&musb->lock, flags); - /* REVISIT always use otg_set_peripheral(), handling - * issues including the root hub one below ... - */ - musb->xceiv.gadget = &musb->g; - musb->xceiv.state = OTG_STATE_B_IDLE; + otg_set_peripheral(musb->xceiv, &musb->g); musb->is_active = 1; /* FIXME this ignores the softconnect flag. Drivers are @@ -1749,6 +1745,8 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver) if (!is_otg_enabled(musb)) musb_start(musb); + otg_set_peripheral(musb->xceiv, &musb->g); + spin_unlock_irqrestore(&musb->lock, flags); if (is_otg_enabled(musb)) { @@ -1762,8 +1760,7 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver) if (retval < 0) { DBG(1, "add_hcd failed, %d\n", retval); spin_lock_irqsave(&musb->lock, flags); - musb->xceiv.gadget = NULL; - musb->xceiv.state = OTG_STATE_UNDEFINED; + otg_set_peripheral(musb->xceiv, NULL); musb->gadget_driver = NULL; musb->g.dev.driver = NULL; spin_unlock_irqrestore(&musb->lock, flags); @@ -1846,8 +1843,9 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) (void) musb_gadget_vbus_draw(&musb->g, 0); - musb->xceiv.state = OTG_STATE_UNDEFINED; + musb->xceiv->state = OTG_STATE_UNDEFINED; stop_activity(musb, driver); + otg_set_peripheral(musb->xceiv, NULL); DBG(3, "unregistering driver %s\n", driver->function); spin_unlock_irqrestore(&musb->lock, flags); @@ -1883,7 +1881,7 @@ EXPORT_SYMBOL(usb_gadget_unregister_driver); void musb_g_resume(struct musb *musb) { musb->is_suspended = 0; - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_B_IDLE: break; case OTG_STATE_B_WAIT_ACON: @@ -1909,10 +1907,10 @@ void musb_g_suspend(struct musb *musb) devctl = musb_readb(musb->mregs, MUSB_DEVCTL); DBG(3, "devctl %02x\n", devctl); - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_B_IDLE: if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) - musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; break; case OTG_STATE_B_PERIPHERAL: musb->is_suspended = 1; @@ -1958,22 +1956,22 @@ void musb_g_disconnect(struct musb *musb) spin_lock(&musb->lock); } - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { default: #ifdef CONFIG_USB_MUSB_OTG DBG(2, "Unhandled disconnect %s, setting a_idle\n", otg_state_string(musb)); - musb->xceiv.state = OTG_STATE_A_IDLE; + musb->xceiv->state = OTG_STATE_A_IDLE; break; case OTG_STATE_A_PERIPHERAL: - musb->xceiv.state = OTG_STATE_A_WAIT_VFALL; + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; break; case OTG_STATE_B_WAIT_ACON: case OTG_STATE_B_HOST: #endif case OTG_STATE_B_PERIPHERAL: case OTG_STATE_B_IDLE: - musb->xceiv.state = OTG_STATE_B_IDLE; + musb->xceiv->state = OTG_STATE_B_IDLE; break; case OTG_STATE_B_SRP_INIT: break; @@ -2029,10 +2027,10 @@ __acquires(musb->lock) * or else after HNP, as A-Device */ if (devctl & MUSB_DEVCTL_BDEVICE) { - musb->xceiv.state = OTG_STATE_B_PERIPHERAL; + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; musb->g.is_a_peripheral = 0; } else if (is_otg_enabled(musb)) { - musb->xceiv.state = OTG_STATE_A_PERIPHERAL; + musb->xceiv->state = OTG_STATE_A_PERIPHERAL; musb->g.is_a_peripheral = 1; } else WARN_ON(1); diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index c1bb192ecbef..0d1f15336a9c 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -2213,7 +2213,7 @@ static int musb_bus_suspend(struct usb_hcd *hcd) { struct musb *musb = hcd_to_musb(hcd); - if (musb->xceiv.state == OTG_STATE_A_SUSPEND) + if (musb->xceiv->state == OTG_STATE_A_SUSPEND) return 0; if (is_host_active(musb) && musb->is_active) { diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c index bf677acc83db..d7e1bc44f00a 100644 --- a/drivers/usb/musb/musb_virthub.c +++ b/drivers/usb/musb/musb_virthub.c @@ -78,18 +78,18 @@ static void musb_port_suspend(struct musb *musb, bool do_suspend) DBG(3, "Root port suspended, power %02x\n", power); musb->port1_status |= USB_PORT_STAT_SUSPEND; - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_HOST: - musb->xceiv.state = OTG_STATE_A_SUSPEND; + musb->xceiv->state = OTG_STATE_A_SUSPEND; musb->is_active = is_otg_enabled(musb) - && musb->xceiv.host->b_hnp_enable; + && musb->xceiv->host->b_hnp_enable; musb_platform_try_idle(musb, 0); break; #ifdef CONFIG_USB_MUSB_OTG case OTG_STATE_B_HOST: - musb->xceiv.state = OTG_STATE_B_WAIT_ACON; + musb->xceiv->state = OTG_STATE_B_WAIT_ACON; musb->is_active = is_otg_enabled(musb) - && musb->xceiv.host->b_hnp_enable; + && musb->xceiv->host->b_hnp_enable; musb_platform_try_idle(musb, 0); break; #endif @@ -116,7 +116,7 @@ static void musb_port_reset(struct musb *musb, bool do_reset) void __iomem *mbase = musb->mregs; #ifdef CONFIG_USB_MUSB_OTG - if (musb->xceiv.state == OTG_STATE_B_IDLE) { + if (musb->xceiv->state == OTG_STATE_B_IDLE) { DBG(2, "HNP: Returning from HNP; no hub reset from b_idle\n"); musb->port1_status &= ~USB_PORT_STAT_RESET; return; @@ -186,14 +186,14 @@ void musb_root_disconnect(struct musb *musb) usb_hcd_poll_rh_status(musb_to_hcd(musb)); musb->is_active = 0; - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_HOST: case OTG_STATE_A_SUSPEND: - musb->xceiv.state = OTG_STATE_A_WAIT_BCON; + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; musb->is_active = 0; break; case OTG_STATE_A_WAIT_VFALL: - musb->xceiv.state = OTG_STATE_B_IDLE; + musb->xceiv->state = OTG_STATE_B_IDLE; break; default: DBG(1, "host disconnect (%s)\n", otg_state_string(musb)); @@ -332,7 +332,7 @@ int musb_hub_control( musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16; usb_hcd_poll_rh_status(musb_to_hcd(musb)); /* NOTE: it might really be A_WAIT_BCON ... */ - musb->xceiv.state = OTG_STATE_A_HOST; + musb->xceiv->state = OTG_STATE_A_HOST; } put_unaligned(cpu_to_le32(musb->port1_status diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index 60924ce08493..a2f443859358 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -61,17 +61,17 @@ static void musb_do_idle(unsigned long _musb) devctl = musb_readb(musb->mregs, MUSB_DEVCTL); - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_WAIT_BCON: devctl &= ~MUSB_DEVCTL_SESSION; musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); devctl = musb_readb(musb->mregs, MUSB_DEVCTL); if (devctl & MUSB_DEVCTL_BDEVICE) { - musb->xceiv.state = OTG_STATE_B_IDLE; + musb->xceiv->state = OTG_STATE_B_IDLE; MUSB_DEV_MODE(musb); } else { - musb->xceiv.state = OTG_STATE_A_IDLE; + musb->xceiv->state = OTG_STATE_A_IDLE; MUSB_HST_MODE(musb); } break; @@ -89,7 +89,7 @@ static void musb_do_idle(unsigned long _musb) musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16; usb_hcd_poll_rh_status(musb_to_hcd(musb)); /* NOTE: it might really be A_WAIT_BCON ... */ - musb->xceiv.state = OTG_STATE_A_HOST; + musb->xceiv->state = OTG_STATE_A_HOST; } break; #endif @@ -97,9 +97,9 @@ static void musb_do_idle(unsigned long _musb) case OTG_STATE_A_HOST: devctl = musb_readb(musb->mregs, MUSB_DEVCTL); if (devctl & MUSB_DEVCTL_BDEVICE) - musb->xceiv.state = OTG_STATE_B_IDLE; + musb->xceiv->state = OTG_STATE_B_IDLE; else - musb->xceiv.state = OTG_STATE_A_WAIT_BCON; + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; #endif default: break; @@ -118,7 +118,7 @@ void musb_platform_try_idle(struct musb *musb, unsigned long timeout) /* Never idle if active, or when VBUS timeout is not set as host */ if (musb->is_active || ((musb->a_wait_bcon == 0) - && (musb->xceiv.state == OTG_STATE_A_WAIT_BCON))) { + && (musb->xceiv->state == OTG_STATE_A_WAIT_BCON))) { DBG(4, "%s active, deleting timer\n", otg_state_string(musb)); del_timer(&musb_idle_timer); last_timer = jiffies; @@ -163,8 +163,8 @@ static void omap_set_vbus(struct musb *musb, int is_on) if (is_on) { musb->is_active = 1; - musb->xceiv.default_a = 1; - musb->xceiv.state = OTG_STATE_A_WAIT_VRISE; + musb->xceiv->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; devctl |= MUSB_DEVCTL_SESSION; MUSB_HST_MODE(musb); @@ -175,8 +175,8 @@ static void omap_set_vbus(struct musb *musb, int is_on) * jumping right to B_IDLE... */ - musb->xceiv.default_a = 0; - musb->xceiv.state = OTG_STATE_B_IDLE; + musb->xceiv->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; devctl &= ~MUSB_DEVCTL_SESSION; MUSB_DEV_MODE(musb); @@ -188,10 +188,6 @@ static void omap_set_vbus(struct musb *musb, int is_on) otg_state_string(musb), musb_readb(musb->mregs, MUSB_DEVCTL)); } -static int omap_set_power(struct otg_transceiver *x, unsigned mA) -{ - return 0; -} static int musb_platform_resume(struct musb *musb); @@ -202,24 +198,6 @@ int musb_platform_set_mode(struct musb *musb, u8 musb_mode) devctl |= MUSB_DEVCTL_SESSION; musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); - switch (musb_mode) { -#ifdef CONFIG_USB_MUSB_HDRC_HCD - case MUSB_HOST: - otg_set_host(&musb->xceiv, musb->xceiv.host); - break; -#endif -#ifdef CONFIG_USB_GADGET_MUSB_HDRC - case MUSB_PERIPHERAL: - otg_set_peripheral(&musb->xceiv, musb->xceiv.gadget); - break; -#endif -#ifdef CONFIG_USB_MUSB_OTG - case MUSB_OTG: - break; -#endif - default: - return -EINVAL; - } return 0; } @@ -231,6 +209,16 @@ int __init musb_platform_init(struct musb *musb) omap_cfg_reg(AE5_2430_USB0HS_STP); #endif + /* We require some kind of external transceiver, hooked + * up through ULPI. TWL4030-family PMICs include one, + * which needs a driver, drivers aren't always needed. + */ + musb->xceiv = otg_get_transceiver(); + if (!musb->xceiv) { + pr_err("HS USB OTG: no transceiver configured\n"); + return -ENODEV; + } + musb_platform_resume(musb); l = omap_readl(OTG_SYSCONFIG); @@ -257,8 +245,6 @@ int __init musb_platform_init(struct musb *musb) if (is_host_enabled(musb)) musb->board_set_vbus = omap_set_vbus; - if (is_peripheral_enabled(musb)) - musb->xceiv.set_power = omap_set_power; musb->a_wait_bcon = MUSB_TIMEOUT_A_WAIT_BCON; setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb); @@ -282,8 +268,7 @@ int musb_platform_suspend(struct musb *musb) l |= ENABLEWAKEUP; /* enable wakeup */ omap_writel(l, OTG_SYSCONFIG); - if (musb->xceiv.set_suspend) - musb->xceiv.set_suspend(&musb->xceiv, 1); + otg_set_suspend(musb->xceiv, 1); if (musb->set_clock) musb->set_clock(musb->clock, 0); @@ -300,8 +285,7 @@ static int musb_platform_resume(struct musb *musb) if (!musb->clock) return 0; - if (musb->xceiv.set_suspend) - musb->xceiv.set_suspend(&musb->xceiv, 0); + otg_set_suspend(musb->xceiv, 0); if (musb->set_clock) musb->set_clock(musb->clock, 1); diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c index 4ac1477d3569..88b587c703e9 100644 --- a/drivers/usb/musb/tusb6010.c +++ b/drivers/usb/musb/tusb6010.c @@ -259,6 +259,8 @@ void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *buf) tusb_fifo_read_unaligned(fifo, buf, len); } +static struct musb *the_musb; + #ifdef CONFIG_USB_GADGET_MUSB_HDRC /* This is used by gadget drivers, and OTG transceiver logic, allowing @@ -269,7 +271,7 @@ void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *buf) */ static int tusb_draw_power(struct otg_transceiver *x, unsigned mA) { - struct musb *musb = container_of(x, struct musb, xceiv); + struct musb *musb = the_musb; void __iomem *tbase = musb->ctrl_base; u32 reg; @@ -419,7 +421,7 @@ static void musb_do_idle(unsigned long _musb) spin_lock_irqsave(&musb->lock, flags); - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_WAIT_BCON: if ((musb->a_wait_bcon != 0) && (musb->idle_timeout == 0 @@ -483,7 +485,7 @@ void musb_platform_try_idle(struct musb *musb, unsigned long timeout) /* Never idle if active, or when VBUS timeout is not set as host */ if (musb->is_active || ((musb->a_wait_bcon == 0) - && (musb->xceiv.state == OTG_STATE_A_WAIT_BCON))) { + && (musb->xceiv->state == OTG_STATE_A_WAIT_BCON))) { DBG(4, "%s active, deleting timer\n", otg_state_string(musb)); del_timer(&musb_idle_timer); last_timer = jiffies; @@ -532,8 +534,8 @@ static void tusb_source_power(struct musb *musb, int is_on) if (musb->set_clock) musb->set_clock(musb->clock, 1); timer = OTG_TIMER_MS(OTG_TIME_A_WAIT_VRISE); - musb->xceiv.default_a = 1; - musb->xceiv.state = OTG_STATE_A_WAIT_VRISE; + musb->xceiv->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; devctl |= MUSB_DEVCTL_SESSION; conf |= TUSB_DEV_CONF_USB_HOST_MODE; @@ -546,24 +548,24 @@ static void tusb_source_power(struct musb *musb, int is_on) /* If ID pin is grounded, we want to be a_idle */ otg_stat = musb_readl(tbase, TUSB_DEV_OTG_STAT); if (!(otg_stat & TUSB_DEV_OTG_STAT_ID_STATUS)) { - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_WAIT_VRISE: case OTG_STATE_A_WAIT_BCON: - musb->xceiv.state = OTG_STATE_A_WAIT_VFALL; + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; break; case OTG_STATE_A_WAIT_VFALL: - musb->xceiv.state = OTG_STATE_A_IDLE; + musb->xceiv->state = OTG_STATE_A_IDLE; break; default: - musb->xceiv.state = OTG_STATE_A_IDLE; + musb->xceiv->state = OTG_STATE_A_IDLE; } musb->is_active = 0; - musb->xceiv.default_a = 1; + musb->xceiv->default_a = 1; MUSB_HST_MODE(musb); } else { musb->is_active = 0; - musb->xceiv.default_a = 0; - musb->xceiv.state = OTG_STATE_B_IDLE; + musb->xceiv->default_a = 0; + musb->xceiv->state = OTG_STATE_B_IDLE; MUSB_DEV_MODE(musb); } @@ -674,7 +676,7 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase) else default_a = is_host_enabled(musb); DBG(2, "Default-%c\n", default_a ? 'A' : 'B'); - musb->xceiv.default_a = default_a; + musb->xceiv->default_a = default_a; tusb_source_power(musb, default_a); /* Don't allow idling immediately */ @@ -686,7 +688,7 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase) if (int_src & TUSB_INT_SRC_VBUS_SENSE_CHNG) { /* B-dev state machine: no vbus ~= disconnect */ - if ((is_otg_enabled(musb) && !musb->xceiv.default_a) + if ((is_otg_enabled(musb) && !musb->xceiv->default_a) || !is_host_enabled(musb)) { #ifdef CONFIG_USB_MUSB_HDRC_HCD /* ? musb_root_disconnect(musb); */ @@ -701,9 +703,9 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase) if (otg_stat & TUSB_DEV_OTG_STAT_SESS_END) { DBG(1, "Forcing disconnect (no interrupt)\n"); - if (musb->xceiv.state != OTG_STATE_B_IDLE) { + if (musb->xceiv->state != OTG_STATE_B_IDLE) { /* INTR_DISCONNECT can hide... */ - musb->xceiv.state = OTG_STATE_B_IDLE; + musb->xceiv->state = OTG_STATE_B_IDLE; musb->int_usb |= MUSB_INTR_DISCONNECT; } musb->is_active = 0; @@ -717,7 +719,7 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase) DBG(2, "vbus change, %s, otg %03x\n", otg_state_string(musb), otg_stat); - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_IDLE: DBG(2, "Got SRP, turning on VBUS\n"); musb_set_vbus(musb, 1); @@ -765,7 +767,7 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase) DBG(4, "%s timer, %03x\n", otg_state_string(musb), otg_stat); - switch (musb->xceiv.state) { + switch (musb->xceiv->state) { case OTG_STATE_A_WAIT_VRISE: /* VBUS has probably been valid for a while now, * but may well have bounced out of range a bit @@ -777,7 +779,7 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase) DBG(2, "devctl %02x\n", devctl); break; } - musb->xceiv.state = OTG_STATE_A_WAIT_BCON; + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; musb->is_active = 0; idle_timeout = jiffies + msecs_to_jiffies(musb->a_wait_bcon); @@ -1093,9 +1095,14 @@ int __init musb_platform_init(struct musb *musb) { struct platform_device *pdev; struct resource *mem; - void __iomem *sync; + void __iomem *sync = NULL; int ret; + usb_nop_xceiv_register(); + musb->xceiv = otg_get_transceiver(); + if (!musb->xceiv) + return -ENODEV; + pdev = to_platform_device(musb->controller); /* dma address for async dma */ @@ -1106,14 +1113,16 @@ int __init musb_platform_init(struct musb *musb) mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!mem) { pr_debug("no sync dma resource?\n"); - return -ENODEV; + ret = -ENODEV; + goto done; } musb->sync = mem->start; sync = ioremap(mem->start, mem->end - mem->start + 1); if (!sync) { pr_debug("ioremap for sync failed\n"); - return -ENOMEM; + ret = -ENOMEM; + goto done; } musb->sync_va = sync; @@ -1126,28 +1135,37 @@ int __init musb_platform_init(struct musb *musb) if (ret) { printk(KERN_ERR "Could not start tusb6010 (%d)\n", ret); - return -ENODEV; + goto done; } musb->isr = tusb_interrupt; if (is_host_enabled(musb)) musb->board_set_vbus = tusb_source_power; - if (is_peripheral_enabled(musb)) - musb->xceiv.set_power = tusb_draw_power; + if (is_peripheral_enabled(musb)) { + musb->xceiv->set_power = tusb_draw_power; + the_musb = musb; + } setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb); +done: + if (ret < 0) { + if (sync) + iounmap(sync); + usb_nop_xceiv_unregister(); + } return ret; } int musb_platform_exit(struct musb *musb) { del_timer_sync(&musb_idle_timer); + the_musb = NULL; if (musb->board_set_power) musb->board_set_power(0); iounmap(musb->sync_va); - + usb_nop_xceiv_unregister(); return 0; } -- cgit v1.2.3 From f7f9d63eac12b345d6243d1d608b7944a05be921 Mon Sep 17 00:00:00 2001 From: David Brownell <dbrownell@users.sourceforge.net> Date: Tue, 31 Mar 2009 12:32:12 -0700 Subject: musb: otg timer cleanup Minor cleanup of OTG timer handling: * unify decls for OTG time constants, in the core header * set up and use that timer in a more normal way * move to the driver struct, so it's usable outside core And tighten use and setup of T(a_wait_bcon) so that if it's used, it's always valid. (If that timer expires, the A-device will stop powering VBUS. For non-OTG systems, that will be a surprise.) No behavioral changes, other than more consistency when applying that core HNP timeout. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/musb_core.c | 41 ++++++++++++++++++++++------------------- drivers/usb/musb/musb_core.h | 14 +++++++++++--- drivers/usb/musb/omap2430.c | 2 -- 3 files changed, 33 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 2460c3986c96..8bd6bb1b04ef 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -112,6 +112,7 @@ #include "davinci.h" #endif +#define TA_WAIT_BCON(m) max_t(int, (m)->a_wait_bcon, OTG_TIME_A_WAIT_BCON) unsigned musb_debug; @@ -287,12 +288,6 @@ const char *otg_state_string(struct musb *musb) #ifdef CONFIG_USB_MUSB_OTG -/* - * See also USB_OTG_1-3.pdf 6.6.5 Timers - * REVISIT: Are the other timers done in the hardware? - */ -#define TB_ASE0_BRST 100 /* Min 3.125 ms */ - /* * Handles OTG hnp timeouts, such as b_ase0_brst */ @@ -320,10 +315,8 @@ void musb_otg_timer_func(unsigned long data) spin_unlock_irqrestore(&musb->lock, flags); } -static DEFINE_TIMER(musb_otg_timer, musb_otg_timer_func, 0, 0); - /* - * Stops the B-device HNP state. Caller must take care of locking. + * Stops the HNP transition. Caller must take care of locking. */ void musb_hnp_stop(struct musb *musb) { @@ -661,11 +654,12 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, musb_g_reset(musb); /* FALLTHROUGH */ case OTG_STATE_A_WAIT_BCON: /* OPT TD.4.7-900ms */ - DBG(1, "HNP: Setting timer as %s\n", - otg_state_string(musb)); - musb_otg_timer.data = (unsigned long)musb; - mod_timer(&musb_otg_timer, jiffies - + msecs_to_jiffies(100)); + /* never use invalid T(a_wait_bcon) */ + DBG(1, "HNP: in %s, %d msec timeout\n", + otg_state_string(musb), + TA_WAIT_BCON(musb)); + mod_timer(&musb->otg_timer, jiffies + + msecs_to_jiffies(TA_WAIT_BCON(musb))); break; case OTG_STATE_A_PERIPHERAL: musb_hnp_stop(musb); @@ -822,9 +816,9 @@ static irqreturn_t musb_stage2_irq(struct musb *musb, u8 int_usb, #ifdef CONFIG_USB_MUSB_OTG musb->xceiv->state = OTG_STATE_B_WAIT_ACON; DBG(1, "HNP: Setting timer for b_ase0_brst\n"); - musb_otg_timer.data = (unsigned long)musb; - mod_timer(&musb_otg_timer, jiffies - + msecs_to_jiffies(TB_ASE0_BRST)); + mod_timer(&musb->otg_timer, jiffies + + msecs_to_jiffies( + OTG_TIME_B_ASE0_BRST)); #endif } break; @@ -1681,7 +1675,8 @@ musb_vbus_store(struct device *dev, struct device_attribute *attr, } spin_lock_irqsave(&musb->lock, flags); - musb->a_wait_bcon = val; + /* force T(a_wait_bcon) to be zero/unlimited *OR* valid */ + musb->a_wait_bcon = val ? max_t(int, val, OTG_TIME_A_WAIT_BCON) : 0 ; if (musb->xceiv->state == OTG_STATE_A_WAIT_BCON) musb->is_active = 0; musb_platform_try_idle(musb, jiffies + msecs_to_jiffies(val)); @@ -1700,10 +1695,13 @@ musb_vbus_show(struct device *dev, struct device_attribute *attr, char *buf) spin_lock_irqsave(&musb->lock, flags); val = musb->a_wait_bcon; + /* FIXME get_vbus_status() is normally #defined as false... + * and is effectively TUSB-specific. + */ vbus = musb_platform_get_vbus_status(musb); spin_unlock_irqrestore(&musb->lock, flags); - return sprintf(buf, "Vbus %s, timeout %lu\n", + return sprintf(buf, "Vbus %s, timeout %lu msec\n", vbus ? "on" : "off", val); } static DEVICE_ATTR(vbus, 0644, musb_vbus_show, musb_vbus_store); @@ -1776,6 +1774,7 @@ allocate_instance(struct device *dev, hcd->uses_new_polling = 1; musb->vbuserr_retry = VBUSERR_RETRY_COUNT; + musb->a_wait_bcon = OTG_TIME_A_WAIT_BCON; #else musb = kzalloc(sizeof *musb, GFP_KERNEL); if (!musb) @@ -1970,6 +1969,10 @@ bad_config: if (status < 0) goto fail2; +#ifdef CONFIG_USB_OTG + setup_timer(&musb->otg_timer, musb_otg_timer_func, (unsigned long) musb); +#endif + /* Init IRQ workqueue before request_irq */ INIT_WORK(&musb->irq_work, musb_irq_work); diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 2b49c989f043..78116fdb5781 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -40,6 +40,7 @@ #include <linux/interrupt.h> #include <linux/smp_lock.h> #include <linux/errno.h> +#include <linux/timer.h> #include <linux/clk.h> #include <linux/device.h> #include <linux/usb/ch9.h> @@ -180,10 +181,15 @@ enum musb_g_ep0_state { MUSB_EP0_STAGE_ACKWAIT, /* after zlp, before statusin */ } __attribute__ ((packed)); -/* OTG protocol constants */ +/* + * OTG protocol constants. See USB OTG 1.3 spec, + * sections 5.5 "Device Timings" and 6.6.5 "Timers". + */ #define OTG_TIME_A_WAIT_VRISE 100 /* msec (max) */ -#define OTG_TIME_A_WAIT_BCON 0 /* 0=infinite; min 1000 msec */ -#define OTG_TIME_A_IDLE_BDIS 200 /* msec (min) */ +#define OTG_TIME_A_WAIT_BCON 1100 /* min 1 second */ +#define OTG_TIME_A_AIDL_BDIS 200 /* min 200 msec */ +#define OTG_TIME_B_ASE0_BRST 100 /* min 3.125 ms */ + /*************************** REGISTER ACCESS ********************************/ @@ -332,6 +338,8 @@ struct musb { struct list_head control; /* of musb_qh */ struct list_head in_bulk; /* of musb_qh */ struct list_head out_bulk; /* of musb_qh */ + + struct timer_list otg_timer; #endif /* called with IRQs blocked; ON/nonzero implies starting a session, diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index a2f443859358..48930f25c450 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -44,7 +44,6 @@ #define get_cpu_rev() 2 #endif -#define MUSB_TIMEOUT_A_WAIT_BCON 1100 static struct timer_list musb_idle_timer; @@ -245,7 +244,6 @@ int __init musb_platform_init(struct musb *musb) if (is_host_enabled(musb)) musb->board_set_vbus = omap_set_vbus; - musb->a_wait_bcon = MUSB_TIMEOUT_A_WAIT_BCON; setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb); -- cgit v1.2.3 From 1de00dae8036dfee44ebea2c38f942fb6072e0b7 Mon Sep 17 00:00:00 2001 From: David Brownell <dbrownell@users.sourceforge.net> Date: Thu, 2 Apr 2009 10:16:11 -0700 Subject: musb: make initial HNP roleswitch work (v2) Minor HNP bugfixes, so the initial role switch works: - A-Device: * disconnect-during-suspend enters A_PERIPHERAL state * kill OTG timer after reset as A_PERIPHERAL ... * ... and also pass that reset to the gadget * once HNP succeeds, clear the "ignore_disconnect" flag * from A_PERIPHERAL, disconnect transitions to A_WAIT_BCON - B-Device: * kill OTG timer on entry to B_HOST state (HNP succeeded) * once HNP succeeds, clear "ignore_disconnect" flag * kick the root hub only _after_ the state is adjusted Other state transitions are left alone. Notably, exit paths from the "roles have switched" state ... A_PERIPHERAL handling of that stays seriously broken. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/musb_core.c | 27 ++++++++++++++++----------- drivers/usb/musb/musb_gadget.c | 2 +- drivers/usb/musb/musb_virthub.c | 11 ++++++++++- 3 files changed, 27 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 8bd6bb1b04ef..93dd23a7eb6c 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -587,28 +587,23 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, if (devctl & MUSB_DEVCTL_LSDEV) musb->port1_status |= USB_PORT_STAT_LOW_SPEED; - if (hcd->status_urb) - usb_hcd_poll_rh_status(hcd); - else - usb_hcd_resume_root_hub(hcd); - - MUSB_HST_MODE(musb); - /* indicate new connection to OTG machine */ switch (musb->xceiv->state) { case OTG_STATE_B_PERIPHERAL: if (int_usb & MUSB_INTR_SUSPEND) { DBG(1, "HNP: SUSPEND+CONNECT, now b_host\n"); - musb->xceiv->state = OTG_STATE_B_HOST; - hcd->self.is_b_host = 1; int_usb &= ~MUSB_INTR_SUSPEND; + goto b_host; } else DBG(1, "CONNECT as b_peripheral???\n"); break; case OTG_STATE_B_WAIT_ACON: - DBG(1, "HNP: Waiting to switch to b_host state\n"); + DBG(1, "HNP: CONNECT, now b_host\n"); +b_host: musb->xceiv->state = OTG_STATE_B_HOST; hcd->self.is_b_host = 1; + musb->ignore_disconnect = 0; + del_timer(&musb->otg_timer); break; default: if ((devctl & MUSB_DEVCTL_VBUS) @@ -618,6 +613,14 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, } break; } + + /* poke the root hub */ + MUSB_HST_MODE(musb); + if (hcd->status_urb) + usb_hcd_poll_rh_status(hcd); + else + usb_hcd_resume_root_hub(hcd); + DBG(1, "CONNECT (%s) devctl %02x\n", otg_state_string(musb), devctl); } @@ -662,7 +665,9 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, + msecs_to_jiffies(TA_WAIT_BCON(musb))); break; case OTG_STATE_A_PERIPHERAL: - musb_hnp_stop(musb); + musb->ignore_disconnect = 0; + del_timer(&musb->otg_timer); + musb_g_reset(musb); break; case OTG_STATE_B_WAIT_ACON: DBG(1, "HNP: RESET (%s), to b_peripheral\n", diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 8dfad1189da2..3c4da75fbc7b 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1964,7 +1964,7 @@ void musb_g_disconnect(struct musb *musb) musb->xceiv->state = OTG_STATE_A_IDLE; break; case OTG_STATE_A_PERIPHERAL: - musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; break; case OTG_STATE_B_WAIT_ACON: case OTG_STATE_B_HOST: diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c index d7e1bc44f00a..c85a82a41d5c 100644 --- a/drivers/usb/musb/musb_virthub.c +++ b/drivers/usb/musb/musb_virthub.c @@ -187,8 +187,17 @@ void musb_root_disconnect(struct musb *musb) musb->is_active = 0; switch (musb->xceiv->state) { - case OTG_STATE_A_HOST: case OTG_STATE_A_SUSPEND: +#ifdef CONFIG_USB_MUSB_OTG + if (is_otg_enabled(musb) + && musb->xceiv->host->b_hnp_enable) { + musb->xceiv->state = OTG_STATE_A_PERIPHERAL; + musb->g.is_a_peripheral = 1; + break; + } +#endif + /* FALLTHROUGH */ + case OTG_STATE_A_HOST: musb->xceiv->state = OTG_STATE_A_WAIT_BCON; musb->is_active = 0; break; -- cgit v1.2.3 From ab983f2a1be582b00f706013f40f658769d0823a Mon Sep 17 00:00:00 2001 From: David Brownell <dbrownell@users.sourceforge.net> Date: Tue, 31 Mar 2009 12:35:09 -0700 Subject: musb: support disconnect after HNP roleswitch Adjust HNP state machines in MUSB driver so that they handle the case where the cable is disconnected. The A-side machine was very wrong (unrecoverable); the B-Side was much less so. - A_PERIPHERAL ... as usual, the non-observability of the ID pin through Mentor's registers makes trouble. We can't go directly to A_WAIT_VFALL to end the session and start the disconnect processing. We can however sense link suspending, go to A_WAIT_BCON, and from there use OTG timeouts to finally trigger that A_WAIT_VFALL transition. (Hoping that nobody reconnects quickly to that port and notices the wrong state.) - B_HOST ... actually clear the Host Request (HR) bit as the messages say, disconnect the peripheral from the root hub, and don't detour through a suspend state. (In some cases this would eventually have cleaned up.) Also adjust the A_SUSPEND transition to respect the A_AIDL_BDIS timeout, so if HNP doesn't trigger quickly enough the A_WAIT_VFALL transition happens as it should. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/musb_core.c | 41 +++++++++++++++++++++++++++++------------ drivers/usb/musb/musb_gadget.c | 2 ++ drivers/usb/musb/musb_virthub.c | 4 ++++ 3 files changed, 35 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 93dd23a7eb6c..b49859623f8d 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -304,9 +304,11 @@ void musb_otg_timer_func(unsigned long data) musb->xceiv->state = OTG_STATE_B_PERIPHERAL; musb->is_active = 0; break; + case OTG_STATE_A_SUSPEND: case OTG_STATE_A_WAIT_BCON: - DBG(1, "HNP: a_wait_bcon timeout; back to a_host\n"); - musb_hnp_stop(musb); + DBG(1, "HNP: %s timeout\n", otg_state_string(musb)); + musb_set_vbus(musb, 0); + musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; break; default: DBG(1, "HNP: Unhandled mode %s\n", otg_state_string(musb)); @@ -324,15 +326,12 @@ void musb_hnp_stop(struct musb *musb) void __iomem *mbase = musb->mregs; u8 reg; + DBG(1, "HNP: stop from %s\n", otg_state_string(musb)); + switch (musb->xceiv->state) { case OTG_STATE_A_PERIPHERAL: - case OTG_STATE_A_WAIT_VFALL: - case OTG_STATE_A_WAIT_BCON: - DBG(1, "HNP: Switching back to A-host\n"); musb_g_disconnect(musb); - musb->xceiv->state = OTG_STATE_A_IDLE; - MUSB_HST_MODE(musb); - musb->is_active = 0; + DBG(1, "HNP: back to %s\n", otg_state_string(musb)); break; case OTG_STATE_B_HOST: DBG(1, "HNP: Disabling HR\n"); @@ -775,7 +774,16 @@ static irqreturn_t musb_stage2_irq(struct musb *musb, u8 int_usb, #endif /* HOST */ #ifdef CONFIG_USB_MUSB_OTG case OTG_STATE_B_HOST: - musb_hnp_stop(musb); + /* REVISIT this behaves for "real disconnect" + * cases; make sure the other transitions from + * from B_HOST act right too. The B_HOST code + * in hnp_stop() is currently not used... + */ + musb_root_disconnect(musb); + musb_to_hcd(musb)->self.is_b_host = 0; + musb->xceiv->state = OTG_STATE_B_PERIPHERAL; + MUSB_DEV_MODE(musb); + musb_g_disconnect(musb); break; case OTG_STATE_A_PERIPHERAL: musb_hnp_stop(musb); @@ -807,10 +815,19 @@ static irqreturn_t musb_stage2_irq(struct musb *musb, u8 int_usb, switch (musb->xceiv->state) { #ifdef CONFIG_USB_MUSB_OTG case OTG_STATE_A_PERIPHERAL: - /* - * We cannot stop HNP here, devctl BDEVICE might be - * still set. + /* We also come here if the cable is removed, since + * this silicon doesn't report ID-no-longer-grounded. + * + * We depend on T(a_wait_bcon) to shut us down, and + * hope users don't do anything dicey during this + * undesired detour through A_WAIT_BCON. */ + musb_hnp_stop(musb); + usb_hcd_resume_root_hub(musb_to_hcd(musb)); + musb_root_disconnect(musb); + musb_platform_try_idle(musb, jiffies + + msecs_to_jiffies(musb->a_wait_bcon + ? : OTG_TIME_A_WAIT_BCON)); break; #endif case OTG_STATE_B_PERIPHERAL: diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 3c4da75fbc7b..858d005fae6d 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1962,9 +1962,11 @@ void musb_g_disconnect(struct musb *musb) DBG(2, "Unhandled disconnect %s, setting a_idle\n", otg_state_string(musb)); musb->xceiv->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); break; case OTG_STATE_A_PERIPHERAL: musb->xceiv->state = OTG_STATE_A_WAIT_BCON; + MUSB_HST_MODE(musb); break; case OTG_STATE_B_WAIT_ACON: case OTG_STATE_B_HOST: diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c index c85a82a41d5c..bfe5fe4ebfee 100644 --- a/drivers/usb/musb/musb_virthub.c +++ b/drivers/usb/musb/musb_virthub.c @@ -83,6 +83,10 @@ static void musb_port_suspend(struct musb *musb, bool do_suspend) musb->xceiv->state = OTG_STATE_A_SUSPEND; musb->is_active = is_otg_enabled(musb) && musb->xceiv->host->b_hnp_enable; + if (musb->is_active) + mod_timer(&musb->otg_timer, jiffies + + msecs_to_jiffies( + OTG_TIME_A_AIDL_BDIS)); musb_platform_try_idle(musb, 0); break; #ifdef CONFIG_USB_MUSB_OTG -- cgit v1.2.3 From d1043a2697effee3054451a9293a376bfb013e4b Mon Sep 17 00:00:00 2001 From: Anand Gadiyar <gadiyar@ti.com> Date: Thu, 2 Apr 2009 12:07:08 -0700 Subject: musb: use dma mode 1 for TX if transfer size equals maxpacket (v2) Currently, with Inventra DMA, we use Mode 0 if transfer size is less than or equal to the endpoint's maxpacket size. This requires that we explicitly set TXPKTRDY for that transfer. However the musb_g_tx code will not set TXPKTRDY twice if the last transfer is exactly equal to maxpacket, even if request->zero is set. Using Mode 1 will solve this; a better fix might be in musb_g_tx(). Without this change, musb will not correctly send out a ZLP if the last transfer is the maxpacket size and request->zero is set. Signed-off-by: Anand Gadiyar <gadiyar@ti.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/musb_gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 858d005fae6d..8b3c4e2ed7b8 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -310,7 +310,7 @@ static void txstate(struct musb *musb, struct musb_request *req) /* setup DMA, then program endpoint CSR */ request_size = min(request->length, musb_ep->dma->max_len); - if (request_size <= musb_ep->packet_sz) + if (request_size < musb_ep->packet_sz) musb_ep->dma->desired_mode = 0; else musb_ep->dma->desired_mode = 1; -- cgit v1.2.3 From a483d7068f661213e9586d4d132fc0e0287118b4 Mon Sep 17 00:00:00 2001 From: Ajay Kumar Gupta <ajay.gupta@ti.com> Date: Fri, 3 Apr 2009 16:16:17 -0700 Subject: musb: add high bandwidth ISO support Tested on OMAP3 host side with Creative (Live! Cam Optia) USB camera which uses high bandwidth isochronous IN endpoints. FIFO mode 4 is updated to provide the needed 4K endpoint buffer without breaking the g_nokia composite gadget configuration. (This is the only gadget driver known to use enough endpoints to notice the change.) Signed-off-by: Ajay Kumar Gupta <ajay.gupta@ti.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> --- drivers/usb/musb/musb_core.c | 19 +++++++++--------- drivers/usb/musb/musb_core.h | 3 +++ drivers/usb/musb/musb_host.c | 47 +++++++++++++++++++++++++++++++++----------- drivers/usb/musb/musb_host.h | 1 + 4 files changed, 48 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index b49859623f8d..554a414f65d1 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -1084,14 +1084,13 @@ static struct fifo_cfg __initdata mode_4_cfg[] = { { .hw_ep_num = 8, .style = FIFO_RX, .maxpacket = 512, }, { .hw_ep_num = 9, .style = FIFO_TX, .maxpacket = 512, }, { .hw_ep_num = 9, .style = FIFO_RX, .maxpacket = 512, }, -{ .hw_ep_num = 10, .style = FIFO_TX, .maxpacket = 512, }, -{ .hw_ep_num = 10, .style = FIFO_RX, .maxpacket = 512, }, -{ .hw_ep_num = 11, .style = FIFO_TX, .maxpacket = 512, }, -{ .hw_ep_num = 11, .style = FIFO_RX, .maxpacket = 512, }, -{ .hw_ep_num = 12, .style = FIFO_TX, .maxpacket = 512, }, -{ .hw_ep_num = 12, .style = FIFO_RX, .maxpacket = 512, }, -{ .hw_ep_num = 13, .style = FIFO_TX, .maxpacket = 512, }, -{ .hw_ep_num = 13, .style = FIFO_RX, .maxpacket = 512, }, +{ .hw_ep_num = 10, .style = FIFO_TX, .maxpacket = 256, }, +{ .hw_ep_num = 10, .style = FIFO_RX, .maxpacket = 64, }, +{ .hw_ep_num = 11, .style = FIFO_TX, .maxpacket = 256, }, +{ .hw_ep_num = 11, .style = FIFO_RX, .maxpacket = 64, }, +{ .hw_ep_num = 12, .style = FIFO_TX, .maxpacket = 256, }, +{ .hw_ep_num = 12, .style = FIFO_RX, .maxpacket = 64, }, +{ .hw_ep_num = 13, .style = FIFO_RXTX, .maxpacket = 4096, }, { .hw_ep_num = 14, .style = FIFO_RXTX, .maxpacket = 1024, }, { .hw_ep_num = 15, .style = FIFO_RXTX, .maxpacket = 1024, }, }; @@ -1351,11 +1350,11 @@ static int __init musb_core_init(u16 musb_type, struct musb *musb) } if (reg & MUSB_CONFIGDATA_HBRXE) { strcat(aInfo, ", HB-ISO Rx"); - strcat(aInfo, " (X)"); /* no driver support */ + musb->hb_iso_rx = true; } if (reg & MUSB_CONFIGDATA_HBTXE) { strcat(aInfo, ", HB-ISO Tx"); - strcat(aInfo, " (X)"); /* no driver support */ + musb->hb_iso_tx = true; } if (reg & MUSB_CONFIGDATA_SOFTCONE) strcat(aInfo, ", SoftConn"); diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 78116fdb5781..f3772ca3b2cf 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -395,6 +395,9 @@ struct musb { unsigned is_multipoint:1; unsigned ignore_disconnect:1; /* during bus resets */ + unsigned hb_iso_rx:1; /* high bandwidth iso rx? */ + unsigned hb_iso_tx:1; /* high bandwidth iso tx? */ + #ifdef C_MP_TX unsigned bulk_split:1; #define can_bulk_split(musb,type) \ diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index 0d1f15336a9c..94a2a350a414 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -605,7 +605,8 @@ musb_rx_reinit(struct musb *musb, struct musb_qh *qh, struct musb_hw_ep *ep) musb_writeb(ep->regs, MUSB_RXTYPE, qh->type_reg); musb_writeb(ep->regs, MUSB_RXINTERVAL, qh->intv_reg); /* NOTE: bulk combining rewrites high bits of maxpacket */ - musb_writew(ep->regs, MUSB_RXMAXP, qh->maxpacket); + musb_writew(ep->regs, MUSB_RXMAXP, + qh->maxpacket | ((qh->hb_mult - 1) << 11)); ep->rx_reinit = 0; } @@ -627,9 +628,10 @@ static bool musb_tx_dma_program(struct dma_controller *dma, csr = musb_readw(epio, MUSB_TXCSR); if (length > pkt_size) { mode = 1; - csr |= MUSB_TXCSR_AUTOSET - | MUSB_TXCSR_DMAMODE - | MUSB_TXCSR_DMAENAB; + csr |= MUSB_TXCSR_DMAMODE | MUSB_TXCSR_DMAENAB; + /* autoset shouldn't be set in high bandwidth */ + if (qh->hb_mult == 1) + csr |= MUSB_TXCSR_AUTOSET; } else { mode = 0; csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAMODE); @@ -1497,6 +1499,10 @@ void musb_host_rx(struct musb *musb, u8 epnum) /* packet error reported later */ iso_err = true; } + } else if (rx_csr & MUSB_RXCSR_INCOMPRX) { + DBG(3, "end %d high bandwidth incomplete ISO packet RX\n", + epnum); + status = -EPROTO; } /* faults abort the transfer */ @@ -1704,7 +1710,11 @@ void musb_host_rx(struct musb *musb, u8 epnum) val &= ~MUSB_RXCSR_H_AUTOREQ; else val |= MUSB_RXCSR_H_AUTOREQ; - val |= MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_DMAENAB; + val |= MUSB_RXCSR_DMAENAB; + + /* autoclear shouldn't be set in high bandwidth */ + if (qh->hb_mult == 1) + val |= MUSB_RXCSR_AUTOCLEAR; musb_writew(epio, MUSB_RXCSR, MUSB_RXCSR_H_WZC_BITS | val); @@ -1790,9 +1800,10 @@ static int musb_schedule( continue; if (is_in) - diff = hw_ep->max_packet_sz_rx - qh->maxpacket; + diff = hw_ep->max_packet_sz_rx; else - diff = hw_ep->max_packet_sz_tx - qh->maxpacket; + diff = hw_ep->max_packet_sz_tx; + diff -= (qh->maxpacket * qh->hb_mult); if (diff >= 0 && best_diff > diff) { best_diff = diff; @@ -1895,15 +1906,27 @@ static int musb_urb_enqueue( qh->is_ready = 1; qh->maxpacket = le16_to_cpu(epd->wMaxPacketSize); + qh->type = usb_endpoint_type(epd); - /* no high bandwidth support yet */ - if (qh->maxpacket & ~0x7ff) { - ret = -EMSGSIZE; - goto done; + /* Bits 11 & 12 of wMaxPacketSize encode high bandwidth multiplier. + * Some musb cores don't support high bandwidth ISO transfers; and + * we don't (yet!) support high bandwidth interrupt transfers. + */ + qh->hb_mult = 1 + ((qh->maxpacket >> 11) & 0x03); + if (qh->hb_mult > 1) { + int ok = (qh->type == USB_ENDPOINT_XFER_ISOC); + + if (ok) + ok = (usb_pipein(urb->pipe) && musb->hb_iso_rx) + || (usb_pipeout(urb->pipe) && musb->hb_iso_tx); + if (!ok) { + ret = -EMSGSIZE; + goto done; + } + qh->maxpacket &= 0x7ff; } qh->epnum = usb_endpoint_num(epd); - qh->type = usb_endpoint_type(epd); /* NOTE: urb->dev->devnum is wrong during SET_ADDRESS */ qh->addr_reg = (u8) usb_pipedevice(urb->pipe); diff --git a/drivers/usb/musb/musb_host.h b/drivers/usb/musb/musb_host.h index 0b7fbcd21963..14b00776638d 100644 --- a/drivers/usb/musb/musb_host.h +++ b/drivers/usb/musb/musb_host.h @@ -67,6 +67,7 @@ struct musb_qh { u8 is_ready; /* safe to modify hw_ep */ u8 type; /* XFERTYPE_* */ u8 epnum; + u8 hb_mult; /* high bandwidth pkts per uf */ u16 maxpacket; u16 frame; /* for periodic schedule */ unsigned iso_idx; /* in urb->iso_frame_desc[] */ -- cgit v1.2.3 From 094c2e6db4be381f708ad8a2e0532d7782f05ea4 Mon Sep 17 00:00:00 2001 From: Mark Adamson <mark.adamson@ftdichip.com> Date: Thu, 9 Apr 2009 15:03:09 +0100 Subject: USB: serial: FTDI: add high speed device support Added support for FTDI's USB 2.0 hi-speed devices - FT2232H (2 interfaces) and FT4232H (4 interfaces), including a new baud rate calculation for these devices which can now achieve up to 12Mbaud by turning off a divide by 2.5 in the baud rate generator of the chips. In order to achieve baud rates of <1200 baud, the divide by 2.5 must be active. The default product ID of the FT2232H is 0x6010 (same as the FT2232C IC). The default PID of the FT4232H is 0x6011. Signed-off-by: Mark J. Adamson <mark.adamson@ftdichip.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/ftdi_sio.c | 90 ++++++++++++++++++++++++++++++++++++++----- drivers/usb/serial/ftdi_sio.h | 10 ++++- 2 files changed, 89 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 59c7501840cb..80ccfa13e0b3 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -47,7 +47,7 @@ /* * Version Information */ -#define DRIVER_VERSION "v1.4.3" +#define DRIVER_VERSION "v1.5.0" #define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>, Bill Ryder <bryder@sgi.com>, Kuba Ober <kuba@mareimbrium.org>" #define DRIVER_DESC "USB FTDI Serial Converters Driver" @@ -82,7 +82,8 @@ struct ftdi_private { int rx_processed; unsigned long rx_bytes; - __u16 interface; /* FT2232C port interface (0 for FT232/245) */ + __u16 interface; /* FT2232C, FT2232H or FT4232H port interface + (0 for FT232/245) */ speed_t force_baud; /* if non-zero, force the baud rate to this value */ @@ -164,6 +165,7 @@ static struct usb_device_id id_table_combined [] = { { USB_DEVICE(FTDI_VID, FTDI_8U232AM_ALT_PID) }, { USB_DEVICE(FTDI_VID, FTDI_232RL_PID) }, { USB_DEVICE(FTDI_VID, FTDI_8U2232C_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_4232H_PID) }, { USB_DEVICE(FTDI_VID, FTDI_MICRO_CHAMELEON_PID) }, { USB_DEVICE(FTDI_VID, FTDI_RELAIS_PID) }, { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_PID) }, @@ -694,6 +696,8 @@ static const char *ftdi_chip_name[] = { [FT232BM] = "FT232BM", [FT2232C] = "FT2232C", [FT232RL] = "FT232RL", + [FT2232H] = "FT2232H", + [FT4232H] = "FT4232H" }; @@ -745,6 +749,8 @@ static unsigned short int ftdi_232am_baud_base_to_divisor(int baud, int base); static unsigned short int ftdi_232am_baud_to_divisor(int baud); static __u32 ftdi_232bm_baud_base_to_divisor(int baud, int base); static __u32 ftdi_232bm_baud_to_divisor(int baud); +static __u32 ftdi_2232h_baud_base_to_divisor(int baud, int base); +static __u32 ftdi_2232h_baud_to_divisor(int baud); static struct usb_serial_driver ftdi_sio_device = { .driver = { @@ -839,6 +845,36 @@ static __u32 ftdi_232bm_baud_to_divisor(int baud) return ftdi_232bm_baud_base_to_divisor(baud, 48000000); } +static __u32 ftdi_2232h_baud_base_to_divisor(int baud, int base) +{ + static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 }; + __u32 divisor; + int divisor3; + + /* hi-speed baud rate is 10-bit sampling instead of 16-bit */ + divisor3 = (base / 10 / baud) * 8; + + divisor = divisor3 >> 3; + divisor |= (__u32)divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) + divisor = 0; + else if (divisor == 0x4001) + divisor = 1; + /* + * Set this bit to turn off a divide by 2.5 on baud rate generator + * This enables baud rates up to 12Mbaud but cannot reach below 1200 + * baud with this bit set + */ + divisor |= 0x00020000; + return divisor; +} + +static __u32 ftdi_2232h_baud_to_divisor(int baud) +{ + return ftdi_2232h_baud_base_to_divisor(baud, 120000000); +} + #define set_mctrl(port, set) update_mctrl((port), (set), 0) #define clear_mctrl(port, clear) update_mctrl((port), 0, (clear)) @@ -997,6 +1033,19 @@ static __u32 get_ftdi_divisor(struct tty_struct *tty, baud = 9600; } break; + case FT2232H: /* FT2232H chip */ + case FT4232H: /* FT4232H chip */ + if ((baud <= 12000000) & (baud >= 1200)) { + div_value = ftdi_2232h_baud_to_divisor(baud); + } else if (baud < 1200) { + div_value = ftdi_232bm_baud_to_divisor(baud); + } else { + dbg("%s - Baud rate too high!", __func__); + div_value = ftdi_232bm_baud_to_divisor(9600); + div_okay = 0; + baud = 9600; + } + break; } /* priv->chip_type */ if (div_okay) { @@ -1197,14 +1246,29 @@ static void ftdi_determine_type(struct usb_serial_port *port) if (interfaces > 1) { int inter; - /* Multiple interfaces. Assume FT2232C. */ - priv->chip_type = FT2232C; + /* Multiple interfaces.*/ + if (version == 0x0800) { + priv->chip_type = FT4232H; + /* Hi-speed - baud clock runs at 120MHz */ + priv->baud_base = 120000000 / 2; + } else if (version == 0x0700) { + priv->chip_type = FT2232H; + /* Hi-speed - baud clock runs at 120MHz */ + priv->baud_base = 120000000 / 2; + } else + priv->chip_type = FT2232C; + /* Determine interface code. */ inter = serial->interface->altsetting->desc.bInterfaceNumber; - if (inter == 0) - priv->interface = PIT_SIOA; - else - priv->interface = PIT_SIOB; + if (inter == 0) { + priv->interface = INTERFACE_A; + } else if (inter == 1) { + priv->interface = INTERFACE_B; + } else if (inter == 2) { + priv->interface = INTERFACE_C; + } else if (inter == 3) { + priv->interface = INTERFACE_D; + } /* BM-type devices have a bug where bcdDevice gets set * to 0x200 when iSerialNumber is 0. */ if (version < 0x500) { @@ -1315,7 +1379,9 @@ static int create_sysfs_attrs(struct usb_serial_port *port) if ((!retval) && (priv->chip_type == FT232BM || priv->chip_type == FT2232C || - priv->chip_type == FT232RL)) { + priv->chip_type == FT232RL || + priv->chip_type == FT2232H || + priv->chip_type == FT4232H)) { retval = device_create_file(&port->dev, &dev_attr_latency_timer); } @@ -1334,7 +1400,9 @@ static void remove_sysfs_attrs(struct usb_serial_port *port) device_remove_file(&port->dev, &dev_attr_event_char); if (priv->chip_type == FT232BM || priv->chip_type == FT2232C || - priv->chip_type == FT232RL) { + priv->chip_type == FT232RL || + priv->chip_type == FT2232H || + priv->chip_type == FT4232H) { device_remove_file(&port->dev, &dev_attr_latency_timer); } } @@ -2333,6 +2401,8 @@ static int ftdi_tiocmget(struct tty_struct *tty, struct file *file) case FT232BM: case FT2232C: case FT232RL: + case FT2232H: + case FT4232H: /* the 8U232AM returns a two byte value (the sio is a 1 byte value) - in the same format as the data returned from the in point */ diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h index 0a09118f315e..f1d440a728a3 100644 --- a/drivers/usb/serial/ftdi_sio.h +++ b/drivers/usb/serial/ftdi_sio.h @@ -10,7 +10,7 @@ * The device is based on the FTDI FT8U100AX chip. It has a DB25 on one side, * USB on the other. * - * Thanx to FTDI (http://www.ftdi.co.uk) for so kindly providing details + * Thanx to FTDI (http://www.ftdichip.com) for so kindly providing details * of the protocol required to talk to the device and ongoing assistence * during development. * @@ -28,6 +28,7 @@ #define FTDI_8U232AM_ALT_PID 0x6006 /* FTDI's alternate PID for above */ #define FTDI_8U2232C_PID 0x6010 /* Dual channel device */ #define FTDI_232RL_PID 0xFBFA /* Product ID for FT232RL */ +#define FTDI_4232H_PID 0x6011 /* Quad channel hi-speed device */ #define FTDI_RELAIS_PID 0xFA10 /* Relais device from Rudolf Gugler */ #define FTDI_NF_RIC_VID 0x0DCD /* Vendor Id */ #define FTDI_NF_RIC_PID 0x0001 /* Product Id */ @@ -876,6 +877,11 @@ #define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */ #define FTDI_SIO_GET_LATENCY_TIMER 10 /* Get the latency timer */ +/* Interface indicies for FT2232, FT2232H and FT4232H devices*/ +#define INTERFACE_A 1 +#define INTERFACE_B 2 +#define INTERFACE_C 3 +#define INTERFACE_D 4 /* * FIC / OpenMoko, Inc. http://wiki.openmoko.org/wiki/Neo1973_Debug_Board_v3 @@ -1039,6 +1045,8 @@ typedef enum { FT232BM = 3, FT2232C = 4, FT232RL = 5, + FT2232H = 6, + FT4232H = 7 } ftdi_chip_type_t; typedef enum { -- cgit v1.2.3 From 895f28badce96cd903026b0076966e3571b6968e Mon Sep 17 00:00:00 2001 From: Mark Adamson <mark.adamson@ftdichip.com> Date: Fri, 1 May 2009 11:48:45 +0100 Subject: USB: ftdi_sio: fix hi-speed device packet size calculation Added a function to set the packet size to be used based on the value from the device endpoint descriptor. The FT2232H and FT4232H hi-speed devices will have wMaxPacketSize of 512 bytes when connected to a USB 2.0 hi-speed host, but will use alternative descriptors with wMaxPacketSize of 64 bytes if connected to a USB 1.1 host or hub. All other FTDI devices have wMaxPacketSize of 64 bytes, except some FT232R and FT245R devices which customers have mistakenly programmed to have wMaxPacketSize of 0 - this is an error and will be overridden to use wMaxPacketSize of 64 bytes. The packet size used is important as it determines where the driver removes the status bytes from the incoming data. If it is incorrect, it will lead to data corruption. Signed-off-by: Mark J. Adamson <mark.adamson@ftdichip.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/ftdi_sio.c | 56 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 80ccfa13e0b3..74cc3c831604 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -95,6 +95,7 @@ struct ftdi_private { unsigned long tx_bytes; unsigned long tx_outstanding_bytes; unsigned long tx_outstanding_urbs; + unsigned short max_packet_size; }; /* struct ftdi_sio_quirk is used by devices requiring special attention. */ @@ -703,7 +704,6 @@ static const char *ftdi_chip_name[] = { /* Constants for read urb and write urb */ #define BUFSZ 512 -#define PKTSZ 64 /* rx_flags */ #define THROTTLED 0x01 @@ -1296,6 +1296,45 @@ static void ftdi_determine_type(struct usb_serial_port *port) } +/* Determine the maximum packet size for the device. This depends on the chip + * type and the USB host capabilities. The value should be obtained from the + * device descriptor as the chip will use the appropriate values for the host.*/ +static void ftdi_set_max_packet_size(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + struct usb_device *udev = serial->dev; + + struct usb_interface *interface = serial->interface; + struct usb_endpoint_descriptor *ep_desc = &interface->cur_altsetting->endpoint[1].desc; + + unsigned num_endpoints; + int i = 0; + + num_endpoints = interface->cur_altsetting->desc.bNumEndpoints; + dev_info(&udev->dev, "Number of endpoints %d\n", num_endpoints); + + /* NOTE: some customers have programmed FT232R/FT245R devices + * with an endpoint size of 0 - not good. In this case, we + * want to override the endpoint descriptor setting and use a + * value of 64 for wMaxPacketSize */ + for (i = 0; i < num_endpoints; i++) { + dev_info(&udev->dev, "Endpoint %d MaxPacketSize %d\n", i+1, + interface->cur_altsetting->endpoint[i].desc.wMaxPacketSize); + ep_desc = &interface->cur_altsetting->endpoint[i].desc; + if (ep_desc->wMaxPacketSize == 0) { + ep_desc->wMaxPacketSize = cpu_to_le16(0x40); + dev_info(&udev->dev, "Overriding wMaxPacketSize on endpoint %d\n", i); + } + } + + /* set max packet size based on descriptor */ + priv->max_packet_size = ep_desc->wMaxPacketSize; + + dev_info(&udev->dev, "Setting MaxPacketSize %d\n", priv->max_packet_size); +} + + /* * *************************************************************************** * Sysfs Attribute @@ -1485,6 +1524,7 @@ static int ftdi_sio_port_probe(struct usb_serial_port *port) usb_set_serial_port_data(port, priv); ftdi_determine_type(port); + ftdi_set_max_packet_size(port); read_latency_timer(port); create_sysfs_attrs(port); return 0; @@ -1740,8 +1780,8 @@ static int ftdi_write(struct tty_struct *tty, struct usb_serial_port *port, if (data_offset > 0) { /* Original sio needs control bytes too... */ transfer_size += (data_offset * - ((count + (PKTSZ - 1 - data_offset)) / - (PKTSZ - data_offset))); + ((count + (priv->max_packet_size - 1 - data_offset)) / + (priv->max_packet_size - data_offset))); } buffer = kmalloc(transfer_size, GFP_ATOMIC); @@ -1763,7 +1803,7 @@ static int ftdi_write(struct tty_struct *tty, struct usb_serial_port *port, if (data_offset > 0) { /* Original sio requires control byte at start of each packet. */ - int user_pktsz = PKTSZ - data_offset; + int user_pktsz = priv->max_packet_size - data_offset; int todo = count; unsigned char *first_byte = buffer; const unsigned char *current_position = buf; @@ -1859,7 +1899,7 @@ static void ftdi_write_bulk_callback(struct urb *urb) data_offset = priv->write_offset; if (data_offset > 0) { /* Subtract the control bytes */ - countback -= (data_offset * DIV_ROUND_UP(countback, PKTSZ)); + countback -= (data_offset * DIV_ROUND_UP(countback, priv->max_packet_size)); } spin_lock_irqsave(&priv->tx_lock, flags); --priv->tx_outstanding_urbs; @@ -1961,7 +2001,7 @@ static void ftdi_read_bulk_callback(struct urb *urb) /* count data bytes, but not status bytes */ countread = urb->actual_length; - countread -= 2 * DIV_ROUND_UP(countread, PKTSZ); + countread -= 2 * DIV_ROUND_UP(countread, priv->max_packet_size); spin_lock_irqsave(&priv->rx_lock, flags); priv->rx_bytes += countread; spin_unlock_irqrestore(&priv->rx_lock, flags); @@ -2034,7 +2074,7 @@ static void ftdi_process_read(struct work_struct *work) need_flip = 0; for (packet_offset = priv->rx_processed; - packet_offset < urb->actual_length; packet_offset += PKTSZ) { + packet_offset < urb->actual_length; packet_offset += priv->max_packet_size) { int length; /* Compare new line status to the old one, signal if different/ @@ -2049,7 +2089,7 @@ static void ftdi_process_read(struct work_struct *work) priv->prev_status = new_status; } - length = min_t(u32, PKTSZ, urb->actual_length-packet_offset)-2; + length = min_t(u32, priv->max_packet_size, urb->actual_length-packet_offset)-2; if (length < 0) { dev_err(&port->dev, "%s - bad packet length: %d\n", __func__, length+2); -- cgit v1.2.3 From 91f8d063d30358fcb76831c238071f7d4b13c35e Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Thu, 16 Apr 2009 15:35:09 -0400 Subject: USB: consolidate usb_unbind_interface and usb_driver_release_interface This patch (as1230) consolidates code in usb_unbind_interface() and usb_driver_release_interface(). In fact, it makes release_interface call unbind_interface, thereby removing the need for duplicated code. It works like this: If the interface has already been registered with the driver core when a driver releases it, then the usual driver-core mechanism will call unbind_interface. If it hasn't been unregistered then we will make the call ourselves. As a nice bonus, drivers now don't have to worry about whether their disconnect method will get called when they release an interface -- it always will. Previously it would be called only if the interface was registered. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/driver.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index d0a21a5f8201..c115eed0fdc3 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -385,7 +385,6 @@ void usb_driver_release_interface(struct usb_driver *driver, struct usb_interface *iface) { struct device *dev = &iface->dev; - struct usb_device *udev = interface_to_usbdev(iface); /* this should never happen, don't release something that's not ours */ if (!dev->driver || dev->driver != &driver->drvwrap.driver) @@ -394,23 +393,19 @@ void usb_driver_release_interface(struct usb_driver *driver, /* don't release from within disconnect() */ if (iface->condition != USB_INTERFACE_BOUND) return; + iface->condition = USB_INTERFACE_UNBINDING; - /* don't release if the interface hasn't been added yet */ + /* Release via the driver core only if the interface + * has already been registered + */ if (device_is_registered(dev)) { - iface->condition = USB_INTERFACE_UNBINDING; device_release_driver(dev); } else { - iface->condition = USB_INTERFACE_UNBOUND; - usb_cancel_queued_reset(iface); + down(&dev->sem); + usb_unbind_interface(dev); + dev->driver = NULL; + up(&dev->sem); } - dev->driver = NULL; - usb_set_intfdata(iface, NULL); - - usb_pm_lock(udev); - iface->condition = USB_INTERFACE_UNBOUND; - mark_quiesced(iface); - iface->needs_remote_wakeup = 0; - usb_pm_unlock(udev); } EXPORT_SYMBOL_GPL(usb_driver_release_interface); -- cgit v1.2.3 From cc71329b3b89b4a5be849b617f2c4f151f0b9213 Mon Sep 17 00:00:00 2001 From: Scott James Remnant <scott@canonical.com> Date: Tue, 21 Apr 2009 17:21:40 +0100 Subject: USB: usbfs: deprecate and hide option for !embedded Modern systems do not use usbfs; the entries within it are files, not device nodes, and do not support ACLs which are the default way to provide access to USB devices to untrusted users. It is replaced by device-nodes maintained by udev in /dev/bus/usb, libusb uses this device nodes. Mark the option as deprecated, and hide entirely for non-embedded builds (which may not be using udev but require raw USB device access). Signed-off-by: Scott James Remnant <scott@canonical.com> Acked-by: Kay Sievers <kay.sievers@vrfy.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/Kconfig | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index e1759d17ac5d..69280c35b5cb 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -28,7 +28,7 @@ comment "Miscellaneous USB options" depends on USB config USB_DEVICEFS - bool "USB device filesystem" + bool "USB device filesystem (DEPRECATED)" if EMBEDDED depends on USB ---help--- If you say Y here (and to "/proc file system support" in the "File @@ -46,11 +46,15 @@ config USB_DEVICEFS For the format of the various /proc/bus/usb/ files, please read <file:Documentation/usb/proc_usb_info.txt>. - Usbfs files can't handle Access Control Lists (ACL), which are the - default way to grant access to USB devices for untrusted users of a - desktop system. The usbfs functionality is replaced by real - device-nodes managed by udev. These nodes live in /dev/bus/usb and - are used by libusb. + Modern Linux systems do not use this. + + Usbfs entries are files and not character devices; usbfs can't + handle Access Control Lists (ACL) which are the default way to + grant access to USB devices for untrusted users of a desktop + system. + + The usbfs functionality is replaced by real device-nodes managed by + udev. These nodes lived in /dev/bus/usb and are used by libusb. config USB_DEVICE_CLASS bool "USB device class-devices (DEPRECATED)" -- cgit v1.2.3 From c5be1b52d9ea6ede4931691bf9f0bd454515aa52 Mon Sep 17 00:00:00 2001 From: Pascal Terjan <pterjan@mandriva.com> Date: Thu, 16 Apr 2009 19:00:45 +0200 Subject: USB: Ignore storage device in modem mode on DWN-652 D-Link DWN-652 in Modem mode exposes 3 interfaces - First one is the USB storage one - Second one is for both control and connection - Third one is unknown This patch avoids usb-storage trying to switch again when already in modem mode, and exposes only 2 ttyUSB instead of 3 by not attaching to the storage interface Signed-off-by: Pascal Terjan <pterjan@mandriva.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/option.c | 15 +++++++++++++++ drivers/usb/storage/option_ms.c | 3 ++- drivers/usb/storage/unusual_devs.h | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 648c481605b1..f11672a25ead 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -43,6 +43,8 @@ #include <linux/usb/serial.h> /* Function prototypes */ +static int option_probe(struct usb_serial *serial, + const struct usb_device_id *id); static int option_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp); static void option_close(struct usb_serial_port *port); @@ -555,6 +557,7 @@ static struct usb_serial_driver option_1port_device = { .usb_driver = &option_driver, .id_table = option_ids, .num_ports = 1, + .probe = option_probe, .open = option_open, .close = option_close, .dtr_rts = option_dtr_rts, @@ -631,6 +634,18 @@ static void __exit option_exit(void) module_init(option_init); module_exit(option_exit); +static int option_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + /* D-Link DWM 652 still exposes CD-Rom emulation interface in modem mode */ + if (serial->dev->descriptor.idVendor == DLINK_VENDOR_ID && + serial->dev->descriptor.idProduct == DLINK_PRODUCT_DWM_652 && + serial->interface->cur_altsetting->desc.bInterfaceClass == 0x8) + return -ENODEV; + + return 0; +} + static void option_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { diff --git a/drivers/usb/storage/option_ms.c b/drivers/usb/storage/option_ms.c index 353f922939a4..126ea3424c03 100644 --- a/drivers/usb/storage/option_ms.c +++ b/drivers/usb/storage/option_ms.c @@ -94,7 +94,8 @@ int option_ms_init(struct us_data *us) */ if (udev->descriptor.bDeviceClass != 0 || udev->descriptor.bDeviceSubClass != 0 || - udev->descriptor.bDeviceProtocol != 0) + udev->descriptor.bDeviceProtocol != 0 || + udev->actconfig->desc.bNumInterfaces == 3) return USB_STOR_TRANSPORT_GOOD; US_DEBUGP("Option MS: option_ms_init called\n"); diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 4b8b69045fe6..1b9c5dd0fb27 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -1385,7 +1385,7 @@ UNUSUAL_DEV( 0x10d6, 0x2200, 0x0100, 0x0100, UNUSUAL_DEV( 0x1186, 0x3e04, 0x0000, 0x0000, "D-Link", "USB Mass Storage", - US_SC_DEVICE, US_PR_DEVICE, option_ms_init, 0), + US_SC_DEVICE, US_PR_DEVICE, option_ms_init, US_FL_IGNORE_DEVICE), /* Reported by Kevin Lloyd <linux@sierrawireless.com> * Entry is needed for the initializer function override, -- cgit v1.2.3 From a864e3aa5d8e1278c05aa93c57dba0debbb46858 Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten <hartleys@visionengravers.com> Date: Wed, 15 Apr 2009 21:34:40 -0400 Subject: USB: core/sysfs: fix sparse warnings Fix 3 sparse warning in drivers/usb/core/sysfs.c. warning: symbol '__mptr' shadows an earlier one Signed-off-by: H Hartley Sweeten <hsweeten@visionengravers.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/sysfs.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index c66789197927..b5c72e458943 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -552,8 +552,8 @@ static struct attribute *dev_string_attrs[] = { static mode_t dev_string_attrs_are_visible(struct kobject *kobj, struct attribute *a, int n) { - struct usb_device *udev = to_usb_device( - container_of(kobj, struct device, kobj)); + struct device *dev = container_of(kobj, struct device, kobj); + struct usb_device *udev = to_usb_device(dev); if (a == &dev_attr_manufacturer.attr) { if (udev->manufacturer == NULL) @@ -585,8 +585,8 @@ static ssize_t read_descriptors(struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) { - struct usb_device *udev = to_usb_device( - container_of(kobj, struct device, kobj)); + struct device *dev = container_of(kobj, struct device, kobj); + struct usb_device *udev = to_usb_device(dev); size_t nleft = count; size_t srclen, n; int cfgno; @@ -786,8 +786,8 @@ static struct attribute *intf_assoc_attrs[] = { static mode_t intf_assoc_attrs_are_visible(struct kobject *kobj, struct attribute *a, int n) { - struct usb_interface *intf = to_usb_interface( - container_of(kobj, struct device, kobj)); + struct device *dev = container_of(kobj, struct device, kobj); + struct usb_interface *intf = to_usb_interface(dev); if (intf->intf_assoc == NULL) return 0; -- cgit v1.2.3 From 15739bb5023ab9373e0c6c7c703dc8c50ead9eca Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke <matthias@kaehlcke.net> Date: Wed, 15 Apr 2009 22:28:41 +0200 Subject: USB: ci13xxx_udc: use helper functions to determine endpoint type and direction Use helper functions to determine the type and direction of an endpoint instead of fiddling with bEndpointAddress and bmAttributes Signed-off-by: Matthias Kaehlcke <matthias@kaehlcke.net> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/ci13xxx_udc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/ci13xxx_udc.c b/drivers/usb/gadget/ci13xxx_udc.c index 38e531ecae4d..c7cb87a6fee2 100644 --- a/drivers/usb/gadget/ci13xxx_udc.c +++ b/drivers/usb/gadget/ci13xxx_udc.c @@ -1977,9 +1977,9 @@ static int ep_enable(struct usb_ep *ep, if (!list_empty(&mEp->qh[mEp->dir].queue)) warn("enabling a non-empty endpoint!"); - mEp->dir = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? TX : RX; - mEp->num = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - mEp->type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + mEp->dir = usb_endpoint_dir_in(desc) ? TX : RX; + mEp->num = usb_endpoint_num(desc); + mEp->type = usb_endpoint_type(desc); mEp->ep.maxpacket = __constant_le16_to_cpu(desc->wMaxPacketSize); -- cgit v1.2.3 From 71de6b63f15924e5aed2de0b975efc5117c0ca75 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke <matthias@kaehlcke.net> Date: Wed, 15 Apr 2009 22:27:49 +0200 Subject: USB: atmel_usba_udc: use helper functions to determine endpoint type and direction Use helper functions to determine the type and direction of an endpoint instead of fiddling with bEndpointAddress and bmAttributes Signed-off-by: Matthias Kaehlcke <matthias@kaehlcke.net> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Acked-by: Haavard Skinnemoen <haavard.skinnemoen@atmel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/atmel_usba_udc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c index 05c913cc3658..7a8f4421924c 100644 --- a/drivers/usb/gadget/atmel_usba_udc.c +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -550,12 +550,12 @@ usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) DBG(DBG_HW, "%s: EPT_SIZE = %lu (maxpacket = %lu)\n", ep->ep.name, ept_cfg, maxpacket); - if ((desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + if (usb_endpoint_dir_in(desc)) { ep->is_in = 1; ept_cfg |= USBA_EPT_DIR_IN; } - switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + switch (usb_endpoint_type(desc)) { case USB_ENDPOINT_XFER_CONTROL: ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL); ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE); -- cgit v1.2.3 From 81c8d8d28d6f2750acd4e41de34aefeb9404431c Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke <matthias@kaehlcke.net> Date: Wed, 15 Apr 2009 22:28:02 +0200 Subject: USB: at91_udc: use helper functions to determine endpoint type and direction Use helper functions to determine the type and direction of an endpoint instead of fiddling with bEndpointAddress and bmAttributes Signed-off-by: Matthias Kaehlcke <matthias@kaehlcke.net> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/at91_udc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/at91_udc.c b/drivers/usb/gadget/at91_udc.c index 0b2bb8f0706d..e954225cd769 100644 --- a/drivers/usb/gadget/at91_udc.c +++ b/drivers/usb/gadget/at91_udc.c @@ -485,7 +485,7 @@ static int at91_ep_enable(struct usb_ep *_ep, return -ESHUTDOWN; } - tmp = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + tmp = usb_endpoint_type(desc); switch (tmp) { case USB_ENDPOINT_XFER_CONTROL: DBG("only one control endpoint\n"); @@ -517,7 +517,7 @@ ok: local_irq_save(flags); /* initialize endpoint to match this descriptor */ - ep->is_in = (desc->bEndpointAddress & USB_DIR_IN) != 0; + ep->is_in = usb_endpoint_dir_in(desc); ep->is_iso = (tmp == USB_ENDPOINT_XFER_ISOC); ep->stopped = 0; if (ep->is_in) -- cgit v1.2.3 From 9ab1565151dff37a7d7687bfe5cfafb62c7d5e49 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke <matthias@kaehlcke.net> Date: Wed, 15 Apr 2009 22:28:21 +0200 Subject: USB: Goku-S: use helper functions to determine endpoint type and direction Use helper functions to determine the type and direction of an endpoint instead of fiddling with bEndpointAddress and bmAttributes Signed-off-by: Matthias Kaehlcke <matthias@kaehlcke.net> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/goku_udc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/goku_udc.c b/drivers/usb/gadget/goku_udc.c index de010c939dbb..112bb40a427c 100644 --- a/drivers/usb/gadget/goku_udc.c +++ b/drivers/usb/gadget/goku_udc.c @@ -110,10 +110,10 @@ goku_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) return -EINVAL; if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) return -ESHUTDOWN; - if (ep->num != (desc->bEndpointAddress & 0x0f)) + if (ep->num != usb_endpoint_num(desc)) return -EINVAL; - switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + switch (usb_endpoint_type(desc)) { case USB_ENDPOINT_XFER_BULK: case USB_ENDPOINT_XFER_INT: break; @@ -142,7 +142,7 @@ goku_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) /* ep1/ep2 dma direction is chosen early; it works in the other * direction, with pio. be cautious with out-dma. */ - ep->is_in = (USB_DIR_IN & desc->bEndpointAddress) != 0; + ep->is_in = usb_endpoint_dir_in(desc); if (ep->is_in) { mode |= 1; ep->dma = (use_dma != 0) && (ep->num == UDC_MSTRD_ENDPOINT); -- cgit v1.2.3 From fa4c86a0dda8dd508f2542d97febe674d2e686cc Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke <matthias@kaehlcke.net> Date: Wed, 15 Apr 2009 22:28:32 +0200 Subject: USB: gadgetfs: use helper functions to determine endpoint type and direction Use helper functions to determine the type and direction of an endpoint instead of fiddling with bEndpointAddress and bmAttributes Signed-off-by: Matthias Kaehlcke <matthias@kaehlcke.net> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/inode.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/inode.c b/drivers/usb/gadget/inode.c index d20937f28a19..7d33f50b5874 100644 --- a/drivers/usb/gadget/inode.c +++ b/drivers/usb/gadget/inode.c @@ -384,9 +384,8 @@ ep_read (struct file *fd, char __user *buf, size_t len, loff_t *ptr) return value; /* halt any endpoint by doing a "wrong direction" i/o call */ - if (data->desc.bEndpointAddress & USB_DIR_IN) { - if ((data->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) - == USB_ENDPOINT_XFER_ISOC) + if (usb_endpoint_dir_in(&data->desc)) { + if (usb_endpoint_xfer_isoc(&data->desc)) return -EINVAL; DBG (data->dev, "%s halt\n", data->name); spin_lock_irq (&data->dev->lock); @@ -428,9 +427,8 @@ ep_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) return value; /* halt any endpoint by doing a "wrong direction" i/o call */ - if (!(data->desc.bEndpointAddress & USB_DIR_IN)) { - if ((data->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) - == USB_ENDPOINT_XFER_ISOC) + if (!usb_endpoint_dir_in(&data->desc)) { + if (usb_endpoint_xfer_isoc(&data->desc)) return -EINVAL; DBG (data->dev, "%s halt\n", data->name); spin_lock_irq (&data->dev->lock); @@ -691,7 +689,7 @@ ep_aio_read(struct kiocb *iocb, const struct iovec *iov, struct ep_data *epdata = iocb->ki_filp->private_data; char *buf; - if (unlikely(epdata->desc.bEndpointAddress & USB_DIR_IN)) + if (unlikely(usb_endpoint_dir_in(&epdata->desc))) return -EINVAL; buf = kmalloc(iocb->ki_left, GFP_KERNEL); @@ -711,7 +709,7 @@ ep_aio_write(struct kiocb *iocb, const struct iovec *iov, size_t len = 0; int i = 0; - if (unlikely(!(epdata->desc.bEndpointAddress & USB_DIR_IN))) + if (unlikely(!usb_endpoint_dir_in(&epdata->desc))) return -EINVAL; buf = kmalloc(iocb->ki_left, GFP_KERNEL); -- cgit v1.2.3 From 1eba67a60d6c95f7eae94930ec369f2837bb5b12 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke <matthias@kaehlcke.net> Date: Wed, 15 Apr 2009 22:28:28 +0200 Subject: USB: UHCI queue: use usb_endpoint_type() use usb_endpoint_type() instead of fiddling manually with bmAttributes Signed-off-by: Matthias Kaehlcke <matthias@kaehlcke.net> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/uhci-q.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c index 3e5807d14ffb..64e57bfe236b 100644 --- a/drivers/usb/host/uhci-q.c +++ b/drivers/usb/host/uhci-q.c @@ -260,7 +260,7 @@ static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci, INIT_LIST_HEAD(&qh->node); if (udev) { /* Normal QH */ - qh->type = hep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + qh->type = usb_endpoint_type(&hep->desc); if (qh->type != USB_ENDPOINT_XFER_ISOC) { qh->dummy_td = uhci_alloc_td(uhci); if (!qh->dummy_td) { -- cgit v1.2.3 From f6d529f936672d341fab14ffb0d8eebeee2d8cd3 Mon Sep 17 00:00:00 2001 From: David Brownell <david-b@pacbell.net> Date: Tue, 21 Apr 2009 20:34:24 -0700 Subject: USB: pxa27x_udc: introduce pxa27x_clear_otgph() Follow pxa27x change in OTGPH handling, and use the newly defined pxa27x_clear_otgph(). Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr> Acked-by: Eric Miao <eric.miao@marvell.com> Acked-by: David Brownell <dbrownell@users.sourceforge.net> --- drivers/usb/gadget/pxa27x_udc.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/pxa27x_udc.c b/drivers/usb/gadget/pxa27x_udc.c index 8cc676ecbb23..ffe6e0afc2ee 100644 --- a/drivers/usb/gadget/pxa27x_udc.c +++ b/drivers/usb/gadget/pxa27x_udc.c @@ -38,7 +38,6 @@ #include <linux/usb.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> -#include <mach/pxa2xx-regs.h> /* FIXME: for PSSR */ #include <mach/udc.h> #include "pxa27x_udc.h" @@ -2479,6 +2478,12 @@ static void pxa_udc_shutdown(struct platform_device *_dev) udc_disable(udc); } +#ifdef CONFIG_CPU_PXA27x +extern void pxa27x_clear_otgph(void); +#else +#define pxa27x_clear_otgph() do {} while (0) +#endif + #ifdef CONFIG_PM /** * pxa_udc_suspend - Suspend udc device @@ -2546,8 +2551,7 @@ static int pxa_udc_resume(struct platform_device *_dev) * Software must configure the USB OTG pad, UDC, and UHC * to the state they were in before entering sleep mode. */ - if (cpu_is_pxa27x()) - PSSR |= PSSR_OTGPH; + pxa27x_clear_otgph(); return 0; } -- cgit v1.2.3 From 9f5351b743716c796d13235651699fb4ec7aa64f Mon Sep 17 00:00:00 2001 From: Robert Jarzmik <robert.jarzmik@free.fr> Date: Tue, 21 Apr 2009 20:34:44 -0700 Subject: USB: pxa27x_udc: compatibility with pxa320 SoC Got pxa27x_udc working on the pxa320 Nomad platform. The problem was that the pxa3xx UDC is not quite compatible with the pxa27x UDC in how it handles back-to-back control packets. The pxa27x probably drops them by default, but the pxa320 does not, and you have to detect it and set the OPC bit to clear the zero-length packet. Signed-off-by: Aric Blumer <aric@sdgsystems.com> Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/Kconfig | 2 +- drivers/usb/gadget/pxa27x_udc.c | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 080bb1e4b847..772dd3b9bab9 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -253,7 +253,7 @@ config USB_PXA25X_SMALL config USB_GADGET_PXA27X boolean "PXA 27x" - depends on ARCH_PXA && PXA27x + depends on ARCH_PXA && (PXA27x || PXA3xx) select USB_OTG_UTILS help Intel's PXA 27x series XScale ARM v5TE processors include diff --git a/drivers/usb/gadget/pxa27x_udc.c b/drivers/usb/gadget/pxa27x_udc.c index ffe6e0afc2ee..51790b06b8ca 100644 --- a/drivers/usb/gadget/pxa27x_udc.c +++ b/drivers/usb/gadget/pxa27x_udc.c @@ -1892,6 +1892,15 @@ static void handle_ep0_ctrl_req(struct pxa_udc *udc, nuke(ep, -EPROTO); + /* + * In the PXA320 manual, in the section about Back-to-Back setup + * packets, it describes this situation. The solution is to set OPC to + * get rid of the status packet, and then continue with the setup + * packet. Generalize to pxa27x CPUs. + */ + if (epout_has_pkt(ep) && (ep_count_bytes_remain(ep) == 0)) + udc_ep_writel(ep, UDCCSR, UDCCSR0_OPC); + /* read SETUP packet */ for (i = 0; i < 2; i++) { if (unlikely(ep_is_empty(ep))) @@ -1965,6 +1974,8 @@ stall: * cleared by software. * - clearing UDCCSR0_OPC always flushes ep0. If in setup stage, never do it * before reading ep0. + * This is true only for PXA27x. This is not true anymore for PXA3xx family + * (check Back-to-Back setup packet in developers guide). * - irq can be called on a "packet complete" event (opc_irq=1), while * UDCCSR0_OPC is not yet raised (delta can be as big as 100ms * from experimentation). @@ -2575,7 +2586,7 @@ static struct platform_driver udc_driver = { static int __init udc_init(void) { - if (!cpu_is_pxa27x()) + if (!cpu_is_pxa27x() && !cpu_is_pxa3xx()) return -ENODEV; printk(KERN_INFO "%s: version %s\n", driver_name, DRIVER_VERSION); -- cgit v1.2.3 From 367815eea4e483d9c3630f69ae0a57e1e32a92b0 Mon Sep 17 00:00:00 2001 From: Robert Jarzmik <robert.jarzmik@free.fr> Date: Tue, 21 Apr 2009 20:41:03 -0700 Subject: USB: pxa27x_udc: single-thread setup requests Since the PXA 27x UDC automatically ACK's some control packets such as SET_INTERFACE, the gadgets may not get a chance to process the request before another control packet is received. The Linux gadgets do not expect to receive setup callbacks out of order. The file storage gadget only saves the "highest" priority request. The PXA27x UDC driver must make sure it only sends one up at a time, allowing the gadget to make changes before continuing. In theory, the host would be NACK'd while the gadget processes the change but the UDC has already ACK'd the request. If another request is sent by the host that is not automatically ACK'd by the UDC, then the throttling happens properly to regain sync. The observed case was the file_storage gadget timing out on a BulkReset request because the SET_INTERFACE was being processed by the gadget. Since SET_INTERFACE is higher priority than BulkReset, the BulkReset was dropped. This was exacerbated by turning on the debug which delayed the fsg signal processing thread. This also fixes the "should never get in WAIT_ACK_SET_CONF_INTERF state here!!!" warning. Reported-by: Vernon Sauder <vernoninhand@gmail.com> Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> index 51790b0..1937d8c 100644 --- drivers/usb/gadget/pxa27x_udc.c | 50 ++++++++++++++++++++++++++++------------- drivers/usb/gadget/pxa27x_udc.h | 2 ++ 2 files changed, 37 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/pxa27x_udc.c b/drivers/usb/gadget/pxa27x_udc.c index 51790b06b8ca..1937d8c7b433 100644 --- a/drivers/usb/gadget/pxa27x_udc.c +++ b/drivers/usb/gadget/pxa27x_udc.c @@ -472,6 +472,23 @@ static inline void udc_clear_mask_UDCCR(struct pxa_udc *udc, int mask) (udccr & UDCCR_MASK_BITS) & ~(mask & UDCCR_MASK_BITS)); } +/** + * ep_write_UDCCSR - set bits in UDCCSR + * @udc: udc device + * @mask: bits to set in UDCCR + * + * Sets bits in UDCCSR (UDCCSR0 and UDCCSR*). + * + * A specific case is applied to ep0 : the ACM bit is always set to 1, for + * SET_INTERFACE and SET_CONFIGURATION. + */ +static inline void ep_write_UDCCSR(struct pxa_ep *ep, int mask) +{ + if (is_ep0(ep)) + mask |= UDCCSR0_ACM; + udc_ep_writel(ep, UDCCSR, mask); +} + /** * ep_count_bytes_remain - get how many bytes in udc endpoint * @ep: udc endpoint @@ -860,7 +877,7 @@ static int read_packet(struct pxa_ep *ep, struct pxa27x_request *req) *buf++ = udc_ep_readl(ep, UDCDR); req->req.actual += count; - udc_ep_writel(ep, UDCCSR, UDCCSR_PC); + ep_write_UDCCSR(ep, UDCCSR_PC); return count; } @@ -968,12 +985,12 @@ static int write_fifo(struct pxa_ep *ep, struct pxa27x_request *req) if (udccsr & UDCCSR_PC) { ep_vdbg(ep, "Clearing Transmit Complete, udccsr=%x\n", udccsr); - udc_ep_writel(ep, UDCCSR, UDCCSR_PC); + ep_write_UDCCSR(ep, UDCCSR_PC); } if (udccsr & UDCCSR_TRN) { ep_vdbg(ep, "Clearing Underrun on, udccsr=%x\n", udccsr); - udc_ep_writel(ep, UDCCSR, UDCCSR_TRN); + ep_write_UDCCSR(ep, UDCCSR_TRN); } count = write_packet(ep, req, max); @@ -995,7 +1012,7 @@ static int write_fifo(struct pxa_ep *ep, struct pxa27x_request *req) } if (is_short) - udc_ep_writel(ep, UDCCSR, UDCCSR_SP); + ep_write_UDCCSR(ep, UDCCSR_SP); /* requests complete when all IN data is in the FIFO */ if (is_last) { @@ -1028,7 +1045,7 @@ static int read_ep0_fifo(struct pxa_ep *ep, struct pxa27x_request *req) while (epout_has_pkt(ep)) { count = read_packet(ep, req); - udc_ep_writel(ep, UDCCSR, UDCCSR0_OPC); + ep_write_UDCCSR(ep, UDCCSR0_OPC); inc_ep_stats_bytes(ep, count, !USB_DIR_IN); is_short = (count < ep->fifo_size); @@ -1073,7 +1090,7 @@ static int write_ep0_fifo(struct pxa_ep *ep, struct pxa27x_request *req) /* Sends either a short packet or a 0 length packet */ if (unlikely(is_short)) - udc_ep_writel(ep, UDCCSR, UDCCSR0_IPR); + ep_write_UDCCSR(ep, UDCCSR0_IPR); ep_dbg(ep, "in %d bytes%s%s, %d left, req=%p, udccsr0=0x%03x\n", count, is_short ? "/S" : "", is_last ? "/L" : "", @@ -1276,7 +1293,7 @@ static int pxa_ep_set_halt(struct usb_ep *_ep, int value) /* FST, FEF bits are the same for control and non control endpoints */ rc = 0; - udc_ep_writel(ep, UDCCSR, UDCCSR_FST | UDCCSR_FEF); + ep_write_UDCCSR(ep, UDCCSR_FST | UDCCSR_FEF); if (is_ep0(ep)) set_ep0state(ep->dev, STALL); @@ -1342,7 +1359,7 @@ static void pxa_ep_fifo_flush(struct usb_ep *_ep) udc_ep_readl(ep, UDCDR); } else { /* most IN status is the same, but ISO can't stall */ - udc_ep_writel(ep, UDCCSR, + ep_write_UDCCSR(ep, UDCCSR_PC | UDCCSR_FEF | UDCCSR_TRN | (EPXFERTYPE_is_ISO(ep) ? 0 : UDCCSR_SST)); } @@ -1727,6 +1744,7 @@ static void udc_enable(struct pxa_udc *udc) memset(&udc->stats, 0, sizeof(udc->stats)); udc_set_mask_UDCCR(udc, UDCCR_UDE); + ep_write_UDCCSR(&udc->pxa_ep[0], UDCCSR0_ACM); udelay(2); if (udc_readl(udc, UDCCR) & UDCCR_EMCE) dev_err(udc->dev, "Configuration errors, udc disabled\n"); @@ -1899,7 +1917,7 @@ static void handle_ep0_ctrl_req(struct pxa_udc *udc, * packet. Generalize to pxa27x CPUs. */ if (epout_has_pkt(ep) && (ep_count_bytes_remain(ep) == 0)) - udc_ep_writel(ep, UDCCSR, UDCCSR0_OPC); + ep_write_UDCCSR(ep, UDCCSR0_OPC); /* read SETUP packet */ for (i = 0; i < 2; i++) { @@ -1927,7 +1945,7 @@ static void handle_ep0_ctrl_req(struct pxa_udc *udc, set_ep0state(udc, OUT_DATA_STAGE); /* Tell UDC to enter Data Stage */ - udc_ep_writel(ep, UDCCSR, UDCCSR0_SA | UDCCSR0_OPC); + ep_write_UDCCSR(ep, UDCCSR0_SA | UDCCSR0_OPC); i = udc->driver->setup(&udc->gadget, &u.r); if (i < 0) @@ -1937,7 +1955,7 @@ out: stall: ep_dbg(ep, "protocol STALL, udccsr0=%03x err %d\n", udc_ep_readl(ep, UDCCSR), i); - udc_ep_writel(ep, UDCCSR, UDCCSR0_FST | UDCCSR0_FTF); + ep_write_UDCCSR(ep, UDCCSR0_FST | UDCCSR0_FTF); set_ep0state(udc, STALL); goto out; } @@ -2008,7 +2026,7 @@ static void handle_ep0(struct pxa_udc *udc, int fifo_irq, int opc_irq) if (udccsr0 & UDCCSR0_SST) { ep_dbg(ep, "clearing stall status\n"); nuke(ep, -EPIPE); - udc_ep_writel(ep, UDCCSR, UDCCSR0_SST); + ep_write_UDCCSR(ep, UDCCSR0_SST); ep0_idle(udc); } @@ -2033,7 +2051,7 @@ static void handle_ep0(struct pxa_udc *udc, int fifo_irq, int opc_irq) break; case IN_DATA_STAGE: /* GET_DESCRIPTOR */ if (epout_has_pkt(ep)) - udc_ep_writel(ep, UDCCSR, UDCCSR0_OPC); + ep_write_UDCCSR(ep, UDCCSR0_OPC); if (req && !ep_is_full(ep)) completed = write_ep0_fifo(ep, req); if (completed) @@ -2046,7 +2064,7 @@ static void handle_ep0(struct pxa_udc *udc, int fifo_irq, int opc_irq) ep0_end_out_req(ep, req); break; case STALL: - udc_ep_writel(ep, UDCCSR, UDCCSR0_FST); + ep_write_UDCCSR(ep, UDCCSR0_FST); break; case IN_STATUS_STAGE: /* @@ -2141,6 +2159,7 @@ static void pxa27x_change_configuration(struct pxa_udc *udc, int config) set_ep0state(udc, WAIT_ACK_SET_CONF_INTERF); udc->driver->setup(&udc->gadget, &req); + ep_write_UDCCSR(&udc->pxa_ep[0], UDCCSR0_AREN); } /** @@ -2169,6 +2188,7 @@ static void pxa27x_change_interface(struct pxa_udc *udc, int iface, int alt) set_ep0state(udc, WAIT_ACK_SET_CONF_INTERF); udc->driver->setup(&udc->gadget, &req); + ep_write_UDCCSR(&udc->pxa_ep[0], UDCCSR0_AREN); } /* @@ -2290,7 +2310,7 @@ static void irq_udc_reset(struct pxa_udc *udc) memset(&udc->stats, 0, sizeof udc->stats); nuke(ep, -EPROTO); - udc_ep_writel(ep, UDCCSR, UDCCSR0_FTF | UDCCSR0_OPC); + ep_write_UDCCSR(ep, UDCCSR0_FTF | UDCCSR0_OPC); ep0_idle(udc); } diff --git a/drivers/usb/gadget/pxa27x_udc.h b/drivers/usb/gadget/pxa27x_udc.h index db58125331da..e25225e26586 100644 --- a/drivers/usb/gadget/pxa27x_udc.h +++ b/drivers/usb/gadget/pxa27x_udc.h @@ -130,6 +130,8 @@ #define UP2OCR_HXOE (1 << 17) /* Transceiver Output Enable */ #define UP2OCR_SEOS (1 << 24) /* Single-Ended Output Select */ +#define UDCCSR0_ACM (1 << 9) /* Ack Control Mode */ +#define UDCCSR0_AREN (1 << 8) /* Ack Response Enable */ #define UDCCSR0_SA (1 << 7) /* Setup Active */ #define UDCCSR0_RNE (1 << 6) /* Receive FIFO Not Empty */ #define UDCCSR0_FST (1 << 5) /* Force Stall */ -- cgit v1.2.3 From 96f90a8b0dc84538ee4a31d3a5b80d758916629e Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten <hartleys@visionengravers.com> Date: Wed, 22 Apr 2009 16:18:59 -0400 Subject: USB: host/ohci-hcd.c: fix sparse warnings Fix sparse warnings in drivers/usb/host/ohci-hcd.c. Four of the following sparse warning are seen when building on ARM due do the macro raw_local_irq_save(): warning: symbol 'temp' shadows an earlier one Signed-off-by: H Hartley Sweeten <hsweeten@visionengravers.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ohci-hcd.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 25db704f3a2a..7635acf0998c 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -571,7 +571,7 @@ static int ohci_init (struct ohci_hcd *ohci) */ static int ohci_run (struct ohci_hcd *ohci) { - u32 mask, temp; + u32 mask, val; int first = ohci->fminterval == 0; struct usb_hcd *hcd = ohci_to_hcd(ohci); @@ -580,8 +580,8 @@ static int ohci_run (struct ohci_hcd *ohci) /* boot firmware should have set this up (5.1.1.3.1) */ if (first) { - temp = ohci_readl (ohci, &ohci->regs->fminterval); - ohci->fminterval = temp & 0x3fff; + val = ohci_readl (ohci, &ohci->regs->fminterval); + ohci->fminterval = val & 0x3fff; if (ohci->fminterval != FI) ohci_dbg (ohci, "fminterval delta %d\n", ohci->fminterval - FI); @@ -600,25 +600,25 @@ static int ohci_run (struct ohci_hcd *ohci) switch (ohci->hc_control & OHCI_CTRL_HCFS) { case OHCI_USB_OPER: - temp = 0; + val = 0; break; case OHCI_USB_SUSPEND: case OHCI_USB_RESUME: ohci->hc_control &= OHCI_CTRL_RWC; ohci->hc_control |= OHCI_USB_RESUME; - temp = 10 /* msec wait */; + val = 10 /* msec wait */; break; // case OHCI_USB_RESET: default: ohci->hc_control &= OHCI_CTRL_RWC; ohci->hc_control |= OHCI_USB_RESET; - temp = 50 /* msec wait */; + val = 50 /* msec wait */; break; } ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); // flush the writes (void) ohci_readl (ohci, &ohci->regs->control); - msleep(temp); + msleep(val); memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); @@ -628,9 +628,9 @@ static int ohci_run (struct ohci_hcd *ohci) retry: /* HC Reset requires max 10 us delay */ ohci_writel (ohci, OHCI_HCR, &ohci->regs->cmdstatus); - temp = 30; /* ... allow extra time */ + val = 30; /* ... allow extra time */ while ((ohci_readl (ohci, &ohci->regs->cmdstatus) & OHCI_HCR) != 0) { - if (--temp == 0) { + if (--val == 0) { spin_unlock_irq (&ohci->lock); ohci_err (ohci, "USB HC reset timed out!\n"); return -1; @@ -699,23 +699,23 @@ retry: ohci_writel (ohci, mask, &ohci->regs->intrenable); /* handle root hub init quirks ... */ - temp = roothub_a (ohci); - temp &= ~(RH_A_PSM | RH_A_OCPM); + val = roothub_a (ohci); + val &= ~(RH_A_PSM | RH_A_OCPM); if (ohci->flags & OHCI_QUIRK_SUPERIO) { /* NSC 87560 and maybe others */ - temp |= RH_A_NOCP; - temp &= ~(RH_A_POTPGT | RH_A_NPS); - ohci_writel (ohci, temp, &ohci->regs->roothub.a); + val |= RH_A_NOCP; + val &= ~(RH_A_POTPGT | RH_A_NPS); + ohci_writel (ohci, val, &ohci->regs->roothub.a); } else if ((ohci->flags & OHCI_QUIRK_AMD756) || (ohci->flags & OHCI_QUIRK_HUB_POWER)) { /* hub power always on; required for AMD-756 and some * Mac platforms. ganged overcurrent reporting, if any. */ - temp |= RH_A_NPS; - ohci_writel (ohci, temp, &ohci->regs->roothub.a); + val |= RH_A_NPS; + ohci_writel (ohci, val, &ohci->regs->roothub.a); } ohci_writel (ohci, RH_HS_LPSC, &ohci->regs->roothub.status); - ohci_writel (ohci, (temp & RH_A_NPS) ? 0 : RH_B_PPCM, + ohci_writel (ohci, (val & RH_A_NPS) ? 0 : RH_B_PPCM, &ohci->regs->roothub.b); // flush those writes (void) ohci_readl (ohci, &ohci->regs->control); @@ -724,7 +724,7 @@ retry: spin_unlock_irq (&ohci->lock); // POTPGT delay is bits 24-31, in 2 ms units. - mdelay ((temp >> 23) & 0x1fe); + mdelay ((val >> 23) & 0x1fe); hcd->state = HC_STATE_RUNNING; if (quirk_zfmicro(ohci)) { -- cgit v1.2.3 From d0f830d30c24175b463fa725ba369a5117fbdd8f Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten <hartleys@visionengravers.com> Date: Wed, 22 Apr 2009 16:03:05 -0400 Subject: USB: hub.c: fix sparse warnings Fix sparse warning in drivers/usb/core/hub.c. The following sparse warning is seen when building on ARM due do the macro raw_local_irq_save(): warning: symbol 'temp' shadows an earlier one Signed-off-by: H Hartley Sweeten <hsweeten@visionengravers.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hub.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index be86ae3f4088..c7a1a1d4bcb1 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -457,13 +457,13 @@ static void hub_tt_kevent (struct work_struct *work) spin_lock_irqsave (&hub->tt.lock, flags); while (--limit && !list_empty (&hub->tt.clear_list)) { - struct list_head *temp; + struct list_head *next; struct usb_tt_clear *clear; struct usb_device *hdev = hub->hdev; int status; - temp = hub->tt.clear_list.next; - clear = list_entry (temp, struct usb_tt_clear, clear_list); + next = hub->tt.clear_list.next; + clear = list_entry (next, struct usb_tt_clear, clear_list); list_del (&clear->clear_list); /* drop lock so HCD can concurrently report other TT errors */ -- cgit v1.2.3 From 84fe6e799deaf14e2c7a941e805cd93d83f90927 Mon Sep 17 00:00:00 2001 From: Tony Cook <tony-cook@bigpond.com> Date: Sat, 18 Apr 2009 22:55:06 +0930 Subject: USB: mos7840: fix debug log messages This patch removes all the unnecessary "\n"s that the debug print statements have, which result in everything appearing double spaced and unreadable in the logs. Signed-off-by: Tony Cook <tony-cook@bigpond.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/mos7840.c | 318 ++++++++++++++++++++++--------------------- 1 file changed, 162 insertions(+), 156 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 10b78a37214f..5fe9fe3df772 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -238,7 +238,7 @@ static int mos7840_set_reg_sync(struct usb_serial_port *port, __u16 reg, { struct usb_device *dev = port->serial->dev; val = val & 0x00ff; - dbg("mos7840_set_reg_sync offset is %x, value %x\n", reg, val); + dbg("mos7840_set_reg_sync offset is %x, value %x", reg, val); return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), MCS_WRREQ, MCS_WR_RTYPE, val, reg, NULL, 0, @@ -260,7 +260,7 @@ static int mos7840_get_reg_sync(struct usb_serial_port *port, __u16 reg, ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), MCS_RDREQ, MCS_RD_RTYPE, 0, reg, val, VENDOR_READ_LENGTH, MOS_WDR_TIMEOUT); - dbg("mos7840_get_reg_sync offset is %x, return val %x\n", reg, *val); + dbg("mos7840_get_reg_sync offset is %x, return val %x", reg, *val); *val = (*val) & 0x00ff; return ret; } @@ -282,18 +282,18 @@ static int mos7840_set_uart_reg(struct usb_serial_port *port, __u16 reg, if (port->serial->num_ports == 4) { val |= (((__u16) port->number - (__u16) (port->serial->minor)) + 1) << 8; - dbg("mos7840_set_uart_reg application number is %x\n", val); + dbg("mos7840_set_uart_reg application number is %x", val); } else { if (((__u16) port->number - (__u16) (port->serial->minor)) == 0) { val |= (((__u16) port->number - (__u16) (port->serial->minor)) + 1) << 8; - dbg("mos7840_set_uart_reg application number is %x\n", + dbg("mos7840_set_uart_reg application number is %x", val); } else { val |= (((__u16) port->number - (__u16) (port->serial->minor)) + 2) << 8; - dbg("mos7840_set_uart_reg application number is %x\n", + dbg("mos7840_set_uart_reg application number is %x", val); } } @@ -315,24 +315,24 @@ static int mos7840_get_uart_reg(struct usb_serial_port *port, __u16 reg, int ret = 0; __u16 Wval; - /* dbg("application number is %4x \n", + /* dbg("application number is %4x", (((__u16)port->number - (__u16)(port->serial->minor))+1)<<8); */ /* Wval is same as application number */ if (port->serial->num_ports == 4) { Wval = (((__u16) port->number - (__u16) (port->serial->minor)) + 1) << 8; - dbg("mos7840_get_uart_reg application number is %x\n", Wval); + dbg("mos7840_get_uart_reg application number is %x", Wval); } else { if (((__u16) port->number - (__u16) (port->serial->minor)) == 0) { Wval = (((__u16) port->number - (__u16) (port->serial->minor)) + 1) << 8; - dbg("mos7840_get_uart_reg application number is %x\n", + dbg("mos7840_get_uart_reg application number is %x", Wval); } else { Wval = (((__u16) port->number - (__u16) (port->serial->minor)) + 2) << 8; - dbg("mos7840_get_uart_reg application number is %x\n", + dbg("mos7840_get_uart_reg application number is %x", Wval); } } @@ -346,11 +346,11 @@ static int mos7840_get_uart_reg(struct usb_serial_port *port, __u16 reg, static void mos7840_dump_serial_port(struct moschip_port *mos7840_port) { - dbg("***************************************\n"); - dbg("SpRegOffset is %2x\n", mos7840_port->SpRegOffset); - dbg("ControlRegOffset is %2x \n", mos7840_port->ControlRegOffset); - dbg("DCRRegOffset is %2x \n", mos7840_port->DcrRegOffset); - dbg("***************************************\n"); + dbg("***************************************"); + dbg("SpRegOffset is %2x", mos7840_port->SpRegOffset); + dbg("ControlRegOffset is %2x", mos7840_port->ControlRegOffset); + dbg("DCRRegOffset is %2x", mos7840_port->DcrRegOffset); + dbg("***************************************"); } @@ -474,12 +474,12 @@ static void mos7840_control_callback(struct urb *urb) goto exit; } - dbg("%s urb buffer size is %d\n", __func__, urb->actual_length); - dbg("%s mos7840_port->MsrLsr is %d port %d\n", __func__, + dbg("%s urb buffer size is %d", __func__, urb->actual_length); + dbg("%s mos7840_port->MsrLsr is %d port %d", __func__, mos7840_port->MsrLsr, mos7840_port->port_num); data = urb->transfer_buffer; regval = (__u8) data[0]; - dbg("%s data is %x\n", __func__, regval); + dbg("%s data is %x", __func__, regval); if (mos7840_port->MsrLsr == 0) mos7840_handle_new_msr(mos7840_port, regval); else if (mos7840_port->MsrLsr == 1) @@ -538,7 +538,7 @@ static void mos7840_interrupt_callback(struct urb *urb) __u16 wval, wreg = 0; int status = urb->status; - dbg("%s", " : Entering\n"); + dbg("%s", " : Entering"); switch (status) { case 0: @@ -570,7 +570,7 @@ static void mos7840_interrupt_callback(struct urb *urb) * Byte 5 FIFO status for both */ if (length && length > 5) { - dbg("%s \n", "Wrong data !!!"); + dbg("%s", "Wrong data !!!"); return; } @@ -587,17 +587,17 @@ static void mos7840_interrupt_callback(struct urb *urb) (__u16) (serial->minor)) + 1) << 8; if (mos7840_port->open) { if (sp[i] & 0x01) { - dbg("SP%d No Interrupt !!!\n", i); + dbg("SP%d No Interrupt !!!", i); } else { switch (sp[i] & 0x0f) { case SERIAL_IIR_RLS: dbg("Serial Port %d: Receiver status error or ", i); - dbg("address bit detected in 9-bit mode\n"); + dbg("address bit detected in 9-bit mode"); mos7840_port->MsrLsr = 1; wreg = LINE_STATUS_REGISTER; break; case SERIAL_IIR_MS: - dbg("Serial Port %d: Modem status change\n", i); + dbg("Serial Port %d: Modem status change", i); mos7840_port->MsrLsr = 0; wreg = MODEM_STATUS_REGISTER; break; @@ -689,7 +689,7 @@ static void mos7840_bulk_in_callback(struct urb *urb) mos7840_port = urb->context; if (!mos7840_port) { - dbg("%s", "NULL mos7840_port pointer \n"); + dbg("%s", "NULL mos7840_port pointer"); mos7840_port->read_urb_busy = false; return; } @@ -702,41 +702,41 @@ static void mos7840_bulk_in_callback(struct urb *urb) port = (struct usb_serial_port *)mos7840_port->port; if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Port Paranoia failed \n"); + dbg("%s", "Port Paranoia failed"); mos7840_port->read_urb_busy = false; return; } serial = mos7840_get_usb_serial(port, __func__); if (!serial) { - dbg("%s\n", "Bad serial pointer "); + dbg("%s", "Bad serial pointer"); mos7840_port->read_urb_busy = false; return; } - dbg("%s\n", "Entering... \n"); + dbg("%s", "Entering... "); data = urb->transfer_buffer; - dbg("%s", "Entering ........... \n"); + dbg("%s", "Entering ..........."); if (urb->actual_length) { tty = tty_port_tty_get(&mos7840_port->port->port); if (tty) { tty_buffer_request_room(tty, urb->actual_length); tty_insert_flip_string(tty, data, urb->actual_length); - dbg(" %s \n", data); + dbg(" %s ", data); tty_flip_buffer_push(tty); tty_kref_put(tty); } mos7840_port->icount.rx += urb->actual_length; smp_wmb(); - dbg("mos7840_port->icount.rx is %d:\n", + dbg("mos7840_port->icount.rx is %d:", mos7840_port->icount.rx); } if (!mos7840_port->read_urb) { - dbg("%s", "URB KILLED !!!\n"); + dbg("%s", "URB KILLED !!!"); mos7840_port->read_urb_busy = false; return; } @@ -777,16 +777,16 @@ static void mos7840_bulk_out_data_callback(struct urb *urb) spin_unlock(&mos7840_port->pool_lock); if (status) { - dbg("nonzero write bulk status received:%d\n", status); + dbg("nonzero write bulk status received:%d", status); return; } if (mos7840_port_paranoia_check(mos7840_port->port, __func__)) { - dbg("%s", "Port Paranoia failed \n"); + dbg("%s", "Port Paranoia failed"); return; } - dbg("%s \n", "Entering ........."); + dbg("%s", "Entering ........."); tty = tty_port_tty_get(&mos7840_port->port->port); if (tty && mos7840_port->open) @@ -830,15 +830,17 @@ static int mos7840_open(struct tty_struct *tty, struct moschip_port *mos7840_port; struct moschip_port *port0; + dbg ("%s enter", __func__); + if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Port Paranoia failed \n"); + dbg("%s", "Port Paranoia failed"); return -ENODEV; } serial = port->serial; if (mos7840_serial_paranoia_check(serial, __func__)) { - dbg("%s", "Serial Paranoia failed \n"); + dbg("%s", "Serial Paranoia failed"); return -ENODEV; } @@ -891,20 +893,20 @@ static int mos7840_open(struct tty_struct *tty, Data = 0x0; status = mos7840_get_reg_sync(port, mos7840_port->SpRegOffset, &Data); if (status < 0) { - dbg("Reading Spreg failed\n"); + dbg("Reading Spreg failed"); return -1; } Data |= 0x80; status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data); if (status < 0) { - dbg("writing Spreg failed\n"); + dbg("writing Spreg failed"); return -1; } Data &= ~0x80; status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data); if (status < 0) { - dbg("writing Spreg failed\n"); + dbg("writing Spreg failed"); return -1; } /* End of block to be checked */ @@ -913,7 +915,7 @@ static int mos7840_open(struct tty_struct *tty, status = mos7840_get_reg_sync(port, mos7840_port->ControlRegOffset, &Data); if (status < 0) { - dbg("Reading Controlreg failed\n"); + dbg("Reading Controlreg failed"); return -1; } Data |= 0x08; /* Driver done bit */ @@ -921,7 +923,7 @@ static int mos7840_open(struct tty_struct *tty, status = mos7840_set_reg_sync(port, mos7840_port->ControlRegOffset, Data); if (status < 0) { - dbg("writing Controlreg failed\n"); + dbg("writing Controlreg failed"); return -1; } /* do register settings here */ @@ -932,21 +934,21 @@ static int mos7840_open(struct tty_struct *tty, Data = 0x00; status = mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data); if (status < 0) { - dbg("disableing interrupts failed\n"); + dbg("disabling interrupts failed"); return -1; } /* Set FIFO_CONTROL_REGISTER to the default value */ Data = 0x00; status = mos7840_set_uart_reg(port, FIFO_CONTROL_REGISTER, Data); if (status < 0) { - dbg("Writing FIFO_CONTROL_REGISTER failed\n"); + dbg("Writing FIFO_CONTROL_REGISTER failed"); return -1; } Data = 0xcf; status = mos7840_set_uart_reg(port, FIFO_CONTROL_REGISTER, Data); if (status < 0) { - dbg("Writing FIFO_CONTROL_REGISTER failed\n"); + dbg("Writing FIFO_CONTROL_REGISTER failed"); return -1; } @@ -1043,12 +1045,12 @@ static int mos7840_open(struct tty_struct *tty, * (can't set it up in mos7840_startup as the * * structures were not set up at that time.) */ - dbg("port number is %d \n", port->number); - dbg("serial number is %d \n", port->serial->minor); - dbg("Bulkin endpoint is %d \n", port->bulk_in_endpointAddress); - dbg("BulkOut endpoint is %d \n", port->bulk_out_endpointAddress); - dbg("Interrupt endpoint is %d \n", port->interrupt_in_endpointAddress); - dbg("port's number in the device is %d\n", mos7840_port->port_num); + dbg("port number is %d", port->number); + dbg("serial number is %d", port->serial->minor); + dbg("Bulkin endpoint is %d", port->bulk_in_endpointAddress); + dbg("BulkOut endpoint is %d", port->bulk_out_endpointAddress); + dbg("Interrupt endpoint is %d", port->interrupt_in_endpointAddress); + dbg("port's number in the device is %d", mos7840_port->port_num); mos7840_port->read_urb = port->read_urb; /* set up our bulk in urb */ @@ -1061,7 +1063,7 @@ static int mos7840_open(struct tty_struct *tty, mos7840_port->read_urb->transfer_buffer_length, mos7840_bulk_in_callback, mos7840_port); - dbg("mos7840_open: bulkin endpoint is %d\n", + dbg("mos7840_open: bulkin endpoint is %d", port->bulk_in_endpointAddress); mos7840_port->read_urb_busy = true; response = usb_submit_urb(mos7840_port->read_urb, GFP_KERNEL); @@ -1087,9 +1089,11 @@ static int mos7840_open(struct tty_struct *tty, mos7840_port->icount.tx = 0; mos7840_port->icount.rx = 0; - dbg("\n\nusb_serial serial:%p mos7840_port:%p\n usb_serial_port port:%p\n\n", + dbg("usb_serial serial:%p mos7840_port:%p\n usb_serial_port port:%p", serial, mos7840_port, port); + dbg ("%s leave", __func__); + return 0; } @@ -1112,16 +1116,16 @@ static int mos7840_chars_in_buffer(struct tty_struct *tty) unsigned long flags; struct moschip_port *mos7840_port; - dbg("%s \n", " mos7840_chars_in_buffer:entering ..........."); + dbg("%s", " mos7840_chars_in_buffer:entering ..........."); if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Invalid port \n"); + dbg("%s", "Invalid port"); return 0; } mos7840_port = mos7840_get_port_private(port); if (mos7840_port == NULL) { - dbg("%s \n", "mos7840_break:leaving ..........."); + dbg("%s", "mos7840_break:leaving ..........."); return 0; } @@ -1148,16 +1152,16 @@ static void mos7840_close(struct usb_serial_port *port) int j; __u16 Data; - dbg("%s\n", "mos7840_close:entering..."); + dbg("%s", "mos7840_close:entering..."); if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Port Paranoia failed \n"); + dbg("%s", "Port Paranoia failed"); return; } serial = mos7840_get_usb_serial(port, __func__); if (!serial) { - dbg("%s", "Serial Paranoia failed \n"); + dbg("%s", "Serial Paranoia failed"); return; } @@ -1185,27 +1189,27 @@ static void mos7840_close(struct usb_serial_port *port) * and interrupt read if they exists */ if (serial->dev) { if (mos7840_port->write_urb) { - dbg("%s", "Shutdown bulk write\n"); + dbg("%s", "Shutdown bulk write"); usb_kill_urb(mos7840_port->write_urb); } if (mos7840_port->read_urb) { - dbg("%s", "Shutdown bulk read\n"); + dbg("%s", "Shutdown bulk read"); usb_kill_urb(mos7840_port->read_urb); mos7840_port->read_urb_busy = false; } if ((&mos7840_port->control_urb)) { - dbg("%s", "Shutdown control read\n"); + dbg("%s", "Shutdown control read"); /*/ usb_kill_urb (mos7840_port->control_urb); */ } } /* if(mos7840_port->ctrl_buf != NULL) */ /* kfree(mos7840_port->ctrl_buf); */ port0->open_ports--; - dbg("mos7840_num_open_ports in close%d:in port%d\n", + dbg("mos7840_num_open_ports in close%d:in port%d", port0->open_ports, port->number); if (port0->open_ports == 0) { if (serial->port[0]->interrupt_in_urb) { - dbg("%s", "Shutdown interrupt_in_urb\n"); + dbg("%s", "Shutdown interrupt_in_urb"); usb_kill_urb(serial->port[0]->interrupt_in_urb); } } @@ -1225,7 +1229,7 @@ static void mos7840_close(struct usb_serial_port *port) mos7840_port->open = 0; - dbg("%s \n", "Leaving ............"); + dbg("%s", "Leaving ............"); } /************************************************************************ @@ -1280,17 +1284,17 @@ static void mos7840_break(struct tty_struct *tty, int break_state) struct usb_serial *serial; struct moschip_port *mos7840_port; - dbg("%s \n", "Entering ..........."); - dbg("mos7840_break: Start\n"); + dbg("%s", "Entering ..........."); + dbg("mos7840_break: Start"); if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Port Paranoia failed \n"); + dbg("%s", "Port Paranoia failed"); return; } serial = mos7840_get_usb_serial(port, __func__); if (!serial) { - dbg("%s", "Serial Paranoia failed \n"); + dbg("%s", "Serial Paranoia failed"); return; } @@ -1310,7 +1314,7 @@ static void mos7840_break(struct tty_struct *tty, int break_state) /* FIXME: no locking on shadowLCR anywhere in driver */ mos7840_port->shadowLCR = data; - dbg("mcs7840_break mos7840_port->shadowLCR is %x\n", + dbg("mcs7840_break mos7840_port->shadowLCR is %x", mos7840_port->shadowLCR); mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, mos7840_port->shadowLCR); @@ -1334,17 +1338,17 @@ static int mos7840_write_room(struct tty_struct *tty) unsigned long flags; struct moschip_port *mos7840_port; - dbg("%s \n", " mos7840_write_room:entering ..........."); + dbg("%s", " mos7840_write_room:entering ..........."); if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Invalid port \n"); - dbg("%s \n", " mos7840_write_room:leaving ..........."); + dbg("%s", "Invalid port"); + dbg("%s", " mos7840_write_room:leaving ..........."); return -1; } mos7840_port = mos7840_get_port_private(port); if (mos7840_port == NULL) { - dbg("%s \n", "mos7840_break:leaving ..........."); + dbg("%s", "mos7840_break:leaving ..........."); return -1; } @@ -1384,16 +1388,16 @@ static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port, /* __u16 Data; */ const unsigned char *current_position = data; unsigned char *data1; - dbg("%s \n", "entering ..........."); - /* dbg("mos7840_write: mos7840_port->shadowLCR is %x\n", + dbg("%s", "entering ..........."); + /* dbg("mos7840_write: mos7840_port->shadowLCR is %x", mos7840_port->shadowLCR); */ #ifdef NOTMOS7840 Data = 0x00; status = mos7840_get_uart_reg(port, LINE_CONTROL_REGISTER, &Data); mos7840_port->shadowLCR = Data; - dbg("mos7840_write: LINE_CONTROL_REGISTER is %x\n", Data); - dbg("mos7840_write: mos7840_port->shadowLCR is %x\n", + dbg("mos7840_write: LINE_CONTROL_REGISTER is %x", Data); + dbg("mos7840_write: mos7840_port->shadowLCR is %x", mos7840_port->shadowLCR); /* Data = 0x03; */ @@ -1407,32 +1411,32 @@ static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port, /* status = mos7840_set_uart_reg(port,DIVISOR_LATCH_LSB,Data); */ Data = 0x00; status = mos7840_get_uart_reg(port, DIVISOR_LATCH_LSB, &Data); - dbg("mos7840_write:DLL value is %x\n", Data); + dbg("mos7840_write:DLL value is %x", Data); Data = 0x0; status = mos7840_get_uart_reg(port, DIVISOR_LATCH_MSB, &Data); - dbg("mos7840_write:DLM value is %x\n", Data); + dbg("mos7840_write:DLM value is %x", Data); Data = Data & ~SERIAL_LCR_DLAB; - dbg("mos7840_write: mos7840_port->shadowLCR is %x\n", + dbg("mos7840_write: mos7840_port->shadowLCR is %x", mos7840_port->shadowLCR); status = mos7840_set_uart_reg(port, LINE_CONTROL_REGISTER, Data); #endif if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Port Paranoia failed \n"); + dbg("%s", "Port Paranoia failed"); return -1; } serial = port->serial; if (mos7840_serial_paranoia_check(serial, __func__)) { - dbg("%s", "Serial Paranoia failed \n"); + dbg("%s", "Serial Paranoia failed"); return -1; } mos7840_port = mos7840_get_port_private(port); if (mos7840_port == NULL) { - dbg("%s", "mos7840_port is NULL\n"); + dbg("%s", "mos7840_port is NULL"); return -1; } @@ -1444,7 +1448,7 @@ static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port, if (!mos7840_port->busy[i]) { mos7840_port->busy[i] = 1; urb = mos7840_port->write_urb_pool[i]; - dbg("\nURB:%d", i); + dbg("URB:%d", i); break; } } @@ -1479,7 +1483,7 @@ static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port, mos7840_bulk_out_data_callback, mos7840_port); data1 = urb->transfer_buffer; - dbg("\nbulkout endpoint is %d", port->bulk_out_endpointAddress); + dbg("bulkout endpoint is %d", port->bulk_out_endpointAddress); /* send it down the pipe */ status = usb_submit_urb(urb, GFP_ATOMIC); @@ -1494,7 +1498,7 @@ static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port, bytes_sent = transfer_size; mos7840_port->icount.tx += transfer_size; smp_wmb(); - dbg("mos7840_port->icount.tx is %d:\n", mos7840_port->icount.tx); + dbg("mos7840_port->icount.tx is %d:", mos7840_port->icount.tx); exit: return bytes_sent; @@ -1513,11 +1517,11 @@ static void mos7840_throttle(struct tty_struct *tty) int status; if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Invalid port \n"); + dbg("%s", "Invalid port"); return; } - dbg("- port %d\n", port->number); + dbg("- port %d", port->number); mos7840_port = mos7840_get_port_private(port); @@ -1525,11 +1529,11 @@ static void mos7840_throttle(struct tty_struct *tty) return; if (!mos7840_port->open) { - dbg("%s\n", "port not opened"); + dbg("%s", "port not opened"); return; } - dbg("%s", "Entering .......... \n"); + dbg("%s", "Entering .........."); /* if we are implementing XON/XOFF, send the stop character */ if (I_IXOFF(tty)) { @@ -1563,7 +1567,7 @@ static void mos7840_unthrottle(struct tty_struct *tty) struct moschip_port *mos7840_port = mos7840_get_port_private(port); if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Invalid port \n"); + dbg("%s", "Invalid port"); return; } @@ -1575,7 +1579,7 @@ static void mos7840_unthrottle(struct tty_struct *tty) return; } - dbg("%s", "Entering .......... \n"); + dbg("%s", "Entering .........."); /* if we are implementing XON/XOFF, send the start character */ if (I_IXOFF(tty)) { @@ -1660,7 +1664,7 @@ static int mos7840_tiocmset(struct tty_struct *tty, struct file *file, status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, mcr); if (status < 0) { - dbg("setting MODEM_CONTROL_REGISTER Failed\n"); + dbg("setting MODEM_CONTROL_REGISTER Failed"); return status; } @@ -1729,11 +1733,11 @@ static int mos7840_calc_baud_rate_divisor(int baudRate, int *divisor, custom++; *divisor = custom; - dbg(" Baud %d = %d\n", baudrate, custom); + dbg(" Baud %d = %d", baudrate, custom); return 0; } - dbg("%s\n", " Baud calculation Failed..."); + dbg("%s", " Baud calculation Failed..."); return -1; #endif } @@ -1759,16 +1763,16 @@ static int mos7840_send_cmd_write_baud_rate(struct moschip_port *mos7840_port, port = (struct usb_serial_port *)mos7840_port->port; if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Invalid port \n"); + dbg("%s", "Invalid port"); return -1; } if (mos7840_serial_paranoia_check(port->serial, __func__)) { - dbg("%s", "Invalid Serial \n"); + dbg("%s", "Invalid Serial"); return -1; } - dbg("%s", "Entering .......... \n"); + dbg("%s", "Entering .........."); number = mos7840_port->port->number - mos7840_port->port->serial->minor; @@ -1784,7 +1788,7 @@ static int mos7840_send_cmd_write_baud_rate(struct moschip_port *mos7840_port, status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data); if (status < 0) { - dbg("Writing spreg failed in set_serial_baud\n"); + dbg("Writing spreg failed in set_serial_baud"); return -1; } #endif @@ -1797,7 +1801,7 @@ static int mos7840_send_cmd_write_baud_rate(struct moschip_port *mos7840_port, status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data); if (status < 0) { - dbg("Writing spreg failed in set_serial_baud\n"); + dbg("Writing spreg failed in set_serial_baud"); return -1; } #endif @@ -1812,14 +1816,14 @@ static int mos7840_send_cmd_write_baud_rate(struct moschip_port *mos7840_port, status = mos7840_get_reg_sync(port, mos7840_port->SpRegOffset, &Data); if (status < 0) { - dbg("reading spreg failed in set_serial_baud\n"); + dbg("reading spreg failed in set_serial_baud"); return -1; } Data = (Data & 0x8f) | clk_sel_val; status = mos7840_set_reg_sync(port, mos7840_port->SpRegOffset, Data); if (status < 0) { - dbg("Writing spreg failed in set_serial_baud\n"); + dbg("Writing spreg failed in set_serial_baud"); return -1; } /* Calculate the Divisor */ @@ -1835,11 +1839,11 @@ static int mos7840_send_cmd_write_baud_rate(struct moschip_port *mos7840_port, /* Write the divisor */ Data = (unsigned char)(divisor & 0xff); - dbg("set_serial_baud Value to write DLL is %x\n", Data); + dbg("set_serial_baud Value to write DLL is %x", Data); mos7840_set_uart_reg(port, DIVISOR_LATCH_LSB, Data); Data = (unsigned char)((divisor & 0xff00) >> 8); - dbg("set_serial_baud Value to write DLM is %x\n", Data); + dbg("set_serial_baud Value to write DLM is %x", Data); mos7840_set_uart_reg(port, DIVISOR_LATCH_MSB, Data); /* Disable access to divisor latch */ @@ -1877,12 +1881,12 @@ static void mos7840_change_port_settings(struct tty_struct *tty, port = (struct usb_serial_port *)mos7840_port->port; if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Invalid port \n"); + dbg("%s", "Invalid port"); return; } if (mos7840_serial_paranoia_check(port->serial, __func__)) { - dbg("%s", "Invalid Serial \n"); + dbg("%s", "Invalid Serial"); return; } @@ -1895,7 +1899,7 @@ static void mos7840_change_port_settings(struct tty_struct *tty, return; } - dbg("%s", "Entering .......... \n"); + dbg("%s", "Entering .........."); lData = LCR_BITS_8; lStop = LCR_STOP_1; @@ -1955,7 +1959,7 @@ static void mos7840_change_port_settings(struct tty_struct *tty, ~(LCR_BITS_MASK | LCR_STOP_MASK | LCR_PAR_MASK); mos7840_port->shadowLCR |= (lData | lParity | lStop); - dbg("mos7840_change_port_settings mos7840_port->shadowLCR is %x\n", + dbg("mos7840_change_port_settings mos7840_port->shadowLCR is %x", mos7840_port->shadowLCR); /* Disable Interrupts */ Data = 0x00; @@ -1997,7 +2001,7 @@ static void mos7840_change_port_settings(struct tty_struct *tty, if (!baud) { /* pick a default, any default... */ - dbg("%s\n", "Picked default baud..."); + dbg("%s", "Picked default baud..."); baud = 9600; } @@ -2020,7 +2024,7 @@ static void mos7840_change_port_settings(struct tty_struct *tty, } wake_up(&mos7840_port->delta_msr_wait); mos7840_port->delta_msr_cond = 1; - dbg("mos7840_change_port_settings mos7840_port->shadowLCR is End %x\n", + dbg("mos7840_change_port_settings mos7840_port->shadowLCR is End %x", mos7840_port->shadowLCR); return; @@ -2040,16 +2044,16 @@ static void mos7840_set_termios(struct tty_struct *tty, unsigned int cflag; struct usb_serial *serial; struct moschip_port *mos7840_port; - dbg("mos7840_set_termios: START\n"); + dbg("mos7840_set_termios: START"); if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Invalid port \n"); + dbg("%s", "Invalid port"); return; } serial = port->serial; if (mos7840_serial_paranoia_check(serial, __func__)) { - dbg("%s", "Invalid Serial \n"); + dbg("%s", "Invalid Serial"); return; } @@ -2063,7 +2067,7 @@ static void mos7840_set_termios(struct tty_struct *tty, return; } - dbg("%s\n", "setting termios - "); + dbg("%s", "setting termios - "); cflag = tty->termios->c_cflag; @@ -2078,7 +2082,7 @@ static void mos7840_set_termios(struct tty_struct *tty, mos7840_change_port_settings(tty, mos7840_port, old_termios); if (!mos7840_port->read_urb) { - dbg("%s", "URB KILLED !!!!!\n"); + dbg("%s", "URB KILLED !!!!!"); return; } @@ -2144,7 +2148,7 @@ static int mos7840_set_modem_info(struct moschip_port *mos7840_port, port = (struct usb_serial_port *)mos7840_port->port; if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Invalid port \n"); + dbg("%s", "Invalid port"); return -1; } @@ -2189,7 +2193,7 @@ static int mos7840_set_modem_info(struct moschip_port *mos7840_port, status = mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data); unlock_kernel(); if (status < 0) { - dbg("setting MODEM_CONTROL_REGISTER Failed\n"); + dbg("setting MODEM_CONTROL_REGISTER Failed"); return -1; } @@ -2274,7 +2278,7 @@ static int mos7840_ioctl(struct tty_struct *tty, struct file *file, int mosret = 0; if (mos7840_port_paranoia_check(port, __func__)) { - dbg("%s", "Invalid port \n"); + dbg("%s", "Invalid port"); return -1; } @@ -2374,9 +2378,8 @@ static int mos7840_calc_num_ports(struct usb_serial *serial) { int mos7840_num_ports = 0; - dbg("numberofendpoints: %d \n", - (int)serial->interface->cur_altsetting->desc.bNumEndpoints); - dbg("numberofendpoints: %d \n", + dbg("numberofendpoints: cur %d, alt %d", + (int)serial->interface->cur_altsetting->desc.bNumEndpoints, (int)serial->interface->altsetting->desc.bNumEndpoints); if (serial->interface->cur_altsetting->desc.bNumEndpoints == 5) { mos7840_num_ports = serial->num_ports = 2; @@ -2385,7 +2388,7 @@ static int mos7840_calc_num_ports(struct usb_serial *serial) serial->num_bulk_out = 4; mos7840_num_ports = serial->num_ports = 4; } - + dbg ("mos7840_num_ports = %d", mos7840_num_ports); return mos7840_num_ports; } @@ -2400,22 +2403,24 @@ static int mos7840_startup(struct usb_serial *serial) int i, status; __u16 Data; - dbg("%s \n", " mos7840_startup :entering.........."); + dbg("%s", "mos7840_startup :Entering.........."); if (!serial) { - dbg("%s\n", "Invalid Handler"); + dbg("%s", "Invalid Handler"); return -1; } dev = serial->dev; - dbg("%s\n", "Entering..."); + dbg("%s", "Entering..."); + dbg ("mos7840_startup: serial = %p", serial); /* we set up the pointers to the endpoints in the mos7840_open * * function, as the structures aren't created yet. */ /* set up port private structures */ for (i = 0; i < serial->num_ports; ++i) { + dbg ("mos7840_startup: configuring port %d............", i); mos7840_port = kzalloc(sizeof(struct moschip_port), GFP_KERNEL); if (mos7840_port == NULL) { dev_err(&dev->dev, "%s - Out of memory\n", __func__); @@ -2473,10 +2478,10 @@ static int mos7840_startup(struct usb_serial *serial) status = mos7840_get_reg_sync(serial->port[i], mos7840_port->ControlRegOffset, &Data); if (status < 0) { - dbg("Reading ControlReg failed status-0x%x\n", status); + dbg("Reading ControlReg failed status-0x%x", status); break; } else - dbg("ControlReg Reading success val is %x, status%d\n", + dbg("ControlReg Reading success val is %x, status%d", Data, status); Data |= 0x08; /* setting driver done bit */ Data |= 0x04; /* sp1_bit to have cts change reflect in @@ -2486,10 +2491,10 @@ static int mos7840_startup(struct usb_serial *serial) status = mos7840_set_reg_sync(serial->port[i], mos7840_port->ControlRegOffset, Data); if (status < 0) { - dbg("Writing ControlReg failed(rx_disable) status-0x%x\n", status); + dbg("Writing ControlReg failed(rx_disable) status-0x%x", status); break; } else - dbg("ControlReg Writing success(rx_disable) status%d\n", + dbg("ControlReg Writing success(rx_disable) status%d", status); /* Write default values in DCR (i.e 0x01 in DCR0, 0x05 in DCR2 @@ -2498,48 +2503,48 @@ static int mos7840_startup(struct usb_serial *serial) status = mos7840_set_reg_sync(serial->port[i], (__u16) (mos7840_port->DcrRegOffset + 0), Data); if (status < 0) { - dbg("Writing DCR0 failed status-0x%x\n", status); + dbg("Writing DCR0 failed status-0x%x", status); break; } else - dbg("DCR0 Writing success status%d\n", status); + dbg("DCR0 Writing success status%d", status); Data = 0x05; status = mos7840_set_reg_sync(serial->port[i], (__u16) (mos7840_port->DcrRegOffset + 1), Data); if (status < 0) { - dbg("Writing DCR1 failed status-0x%x\n", status); + dbg("Writing DCR1 failed status-0x%x", status); break; } else - dbg("DCR1 Writing success status%d\n", status); + dbg("DCR1 Writing success status%d", status); Data = 0x24; status = mos7840_set_reg_sync(serial->port[i], (__u16) (mos7840_port->DcrRegOffset + 2), Data); if (status < 0) { - dbg("Writing DCR2 failed status-0x%x\n", status); + dbg("Writing DCR2 failed status-0x%x", status); break; } else - dbg("DCR2 Writing success status%d\n", status); + dbg("DCR2 Writing success status%d", status); /* write values in clkstart0x0 and clkmulti 0x20 */ Data = 0x0; status = mos7840_set_reg_sync(serial->port[i], CLK_START_VALUE_REGISTER, Data); if (status < 0) { - dbg("Writing CLK_START_VALUE_REGISTER failed status-0x%x\n", status); + dbg("Writing CLK_START_VALUE_REGISTER failed status-0x%x", status); break; } else - dbg("CLK_START_VALUE_REGISTER Writing success status%d\n", status); + dbg("CLK_START_VALUE_REGISTER Writing success status%d", status); Data = 0x20; status = mos7840_set_reg_sync(serial->port[i], CLK_MULTI_REGISTER, Data); if (status < 0) { - dbg("Writing CLK_MULTI_REGISTER failed status-0x%x\n", + dbg("Writing CLK_MULTI_REGISTER failed status-0x%x", status); goto error; } else - dbg("CLK_MULTI_REGISTER Writing success status%d\n", + dbg("CLK_MULTI_REGISTER Writing success status%d", status); /* write value 0x0 to scratchpad register */ @@ -2547,11 +2552,11 @@ static int mos7840_startup(struct usb_serial *serial) status = mos7840_set_uart_reg(serial->port[i], SCRATCH_PAD_REGISTER, Data); if (status < 0) { - dbg("Writing SCRATCH_PAD_REGISTER failed status-0x%x\n", + dbg("Writing SCRATCH_PAD_REGISTER failed status-0x%x", status); break; } else - dbg("SCRATCH_PAD_REGISTER Writing success status%d\n", + dbg("SCRATCH_PAD_REGISTER Writing success status%d", status); /* Zero Length flag register */ @@ -2562,30 +2567,30 @@ static int mos7840_startup(struct usb_serial *serial) status = mos7840_set_reg_sync(serial->port[i], (__u16) (ZLP_REG1 + ((__u16)mos7840_port->port_num)), Data); - dbg("ZLIP offset%x\n", + dbg("ZLIP offset %x", (__u16) (ZLP_REG1 + ((__u16) mos7840_port->port_num))); if (status < 0) { - dbg("Writing ZLP_REG%d failed status-0x%x\n", + dbg("Writing ZLP_REG%d failed status-0x%x", i + 2, status); break; } else - dbg("ZLP_REG%d Writing success status%d\n", + dbg("ZLP_REG%d Writing success status%d", i + 2, status); } else { Data = 0xff; status = mos7840_set_reg_sync(serial->port[i], (__u16) (ZLP_REG1 + ((__u16)mos7840_port->port_num) - 0x1), Data); - dbg("ZLIP offset%x\n", + dbg("ZLIP offset %x", (__u16) (ZLP_REG1 + ((__u16) mos7840_port->port_num) - 0x1)); if (status < 0) { - dbg("Writing ZLP_REG%d failed status-0x%x\n", + dbg("Writing ZLP_REG%d failed status-0x%x", i + 1, status); break; } else - dbg("ZLP_REG%d Writing success status%d\n", + dbg("ZLP_REG%d Writing success status%d", i + 1, status); } @@ -2599,15 +2604,16 @@ static int mos7840_startup(struct usb_serial *serial) goto error; } } + dbg ("mos7840_startup: all ports configured..........."); /* Zero Length flag enable */ Data = 0x0f; status = mos7840_set_reg_sync(serial->port[0], ZLP_REG5, Data); if (status < 0) { - dbg("Writing ZLP_REG5 failed status-0x%x\n", status); + dbg("Writing ZLP_REG5 failed status-0x%x", status); goto error; } else - dbg("ZLP_REG5 Writing success status%d\n", status); + dbg("ZLP_REG5 Writing success status%d", status); /* setting configuration feature to one */ usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), @@ -2636,10 +2642,10 @@ static void mos7840_shutdown(struct usb_serial *serial) int i; unsigned long flags; struct moschip_port *mos7840_port; - dbg("%s \n", " shutdown :entering.........."); + dbg("%s", " shutdown :entering.........."); if (!serial) { - dbg("%s", "Invalid Handler \n"); + dbg("%s", "Invalid Handler"); return; } @@ -2663,7 +2669,7 @@ static void mos7840_shutdown(struct usb_serial *serial) mos7840_set_port_private(serial->port[i], NULL); } - dbg("%s\n", "Thank u :: "); + dbg("%s", "Thank u :: "); } @@ -2714,7 +2720,7 @@ static int __init moschip7840_init(void) { int retval; - dbg("%s \n", " mos7840_init :entering.........."); + dbg("%s", " mos7840_init :entering.........."); /* Register with the usb serial */ retval = usb_serial_register(&moschip7840_4port_device); @@ -2722,14 +2728,14 @@ static int __init moschip7840_init(void) if (retval) goto failed_port_device_register; - dbg("%s\n", "Entring..."); + dbg("%s", "Entering..."); printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" DRIVER_DESC "\n"); /* Register with the usb */ retval = usb_register(&io_driver); if (retval == 0) { - dbg("%s\n", "Leaving..."); + dbg("%s", "Leaving..."); return 0; } usb_serial_deregister(&moschip7840_4port_device); @@ -2744,13 +2750,13 @@ failed_port_device_register: static void __exit moschip7840_exit(void) { - dbg("%s \n", " mos7840_exit :entering.........."); + dbg("%s", " mos7840_exit :entering.........."); usb_deregister(&io_driver); usb_serial_deregister(&moschip7840_4port_device); - dbg("%s\n", "Entring..."); + dbg("%s", "Entering..."); } module_init(moschip7840_init); -- cgit v1.2.3 From 00048b8bde5a6cbd9c3a76f272cc9ddb55705e37 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Fri, 24 Apr 2009 14:56:26 -0700 Subject: USB: add usb debugfs directory Add a common usb directory in debugfs that the usb subsystem can use. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/usb.c | 22 ++++++++++++++++++++++ include/linux/usb.h | 3 +++ 2 files changed, 25 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 7eee400d3e32..5f6873f5f268 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -34,6 +34,7 @@ #include <linux/usb.h> #include <linux/mutex.h> #include <linux/workqueue.h> +#include <linux/debugfs.h> #include <asm/io.h> #include <linux/scatterlist.h> @@ -1001,6 +1002,22 @@ static struct notifier_block usb_bus_nb = { .notifier_call = usb_bus_notify, }; +struct dentry *usb_debug_root; +EXPORT_SYMBOL_GPL(usb_debug_root); + +static int usb_debugfs_init(void) +{ + usb_debug_root = debugfs_create_dir("usb", NULL); + if (!usb_debug_root) + return -ENOENT; + return 0; +} + +static void usb_debugfs_cleanup(void) +{ + debugfs_remove(usb_debug_root); +} + /* * Init */ @@ -1012,6 +1029,10 @@ static int __init usb_init(void) return 0; } + retval = usb_debugfs_init(); + if (retval) + goto out; + retval = ksuspend_usb_init(); if (retval) goto out; @@ -1083,6 +1104,7 @@ static void __exit usb_exit(void) bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); bus_unregister(&usb_bus_type); ksuspend_usb_cleanup(); + usb_debugfs_cleanup(); } subsys_initcall(usb_init); diff --git a/include/linux/usb.h b/include/linux/usb.h index 3aa2cd1f8d08..29060dad81e6 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1558,6 +1558,9 @@ extern void usb_unregister_notify(struct notifier_block *nb); #define err(format, arg...) printk(KERN_ERR KBUILD_MODNAME ": " \ format "\n" , ## arg) +/* debugfs stuff */ +extern struct dentry *usb_debug_root; + #endif /* __KERNEL__ */ #endif -- cgit v1.2.3 From 08f4e586b9db4f398af1133a7b7457607539b958 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Fri, 24 Apr 2009 15:13:18 -0700 Subject: USB: EHCI: use the new usb debugfs directory All usb debugfs files should be behind the usb directory, not at the root of debugfs. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index c637207a1c80..a2ca9cbf2809 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1097,7 +1097,7 @@ static int __init ehci_hcd_init(void) sizeof(struct ehci_itd), sizeof(struct ehci_sitd)); #ifdef DEBUG - ehci_debug_root = debugfs_create_dir("ehci", NULL); + ehci_debug_root = debugfs_create_dir("ehci", usb_debug_root); if (!ehci_debug_root) { retval = -ENOENT; goto err_debug; -- cgit v1.2.3 From 66536ab36895f7da718693c21f2bde3247c8cf07 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Fri, 24 Apr 2009 15:14:25 -0700 Subject: USB: FHCI: use the new usb debugfs directory All usb debugfs files should be behind the usb directory, not at the root of debugfs. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/fhci-dbg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/fhci-dbg.c b/drivers/usb/host/fhci-dbg.c index ea8a4255c5da..e799f86dab11 100644 --- a/drivers/usb/host/fhci-dbg.c +++ b/drivers/usb/host/fhci-dbg.c @@ -108,7 +108,7 @@ void fhci_dfs_create(struct fhci_hcd *fhci) { struct device *dev = fhci_to_hcd(fhci)->self.controller; - fhci->dfs_root = debugfs_create_dir(dev_name(dev), NULL); + fhci->dfs_root = debugfs_create_dir(dev_name(dev), usb_debug_root); if (!fhci->dfs_root) { WARN_ON(1); return; -- cgit v1.2.3 From 485f4f397544a26ad10ec2a3c7fd9dfe009fe602 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Fri, 24 Apr 2009 15:14:38 -0700 Subject: USB: OHCI: use the new usb debugfs directory All usb debugfs files should be behind the usb directory, not at the root of debugfs. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ohci-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 7635acf0998c..58151687d351 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -1105,7 +1105,7 @@ static int __init ohci_hcd_mod_init(void) set_bit(USB_OHCI_LOADED, &usb_hcds_loaded); #ifdef DEBUG - ohci_debug_root = debugfs_create_dir("ohci", NULL); + ohci_debug_root = debugfs_create_dir("ohci", usb_debug_root); if (!ohci_debug_root) { retval = -ENOENT; goto error_debug; -- cgit v1.2.3 From ec20df2e8907950f45fa538fa5c62a254e686875 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Fri, 24 Apr 2009 15:15:00 -0700 Subject: USB: UHCI: use the new usb debugfs directory All usb debugfs files should be behind the usb directory, not at the root of debugfs. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/uhci-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index cf5e4cf7ea42..f2fd709fcce7 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -961,7 +961,7 @@ static int __init uhci_hcd_init(void) errbuf = kmalloc(ERRBUF_LEN, GFP_KERNEL); if (!errbuf) goto errbuf_failed; - uhci_debugfs_root = debugfs_create_dir("uhci", NULL); + uhci_debugfs_root = debugfs_create_dir("uhci", usb_debug_root); if (!uhci_debugfs_root) goto debug_failed; } -- cgit v1.2.3 From f49ce96f11112a84c16ac217490ebd6f8d9a8977 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Fri, 24 Apr 2009 15:15:49 -0700 Subject: USB: usbmon: use the new usb debugfs directory All usb debugfs files should be behind the usb directory, not at the root of debugfs. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/mon/mon_text.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/mon/mon_text.c b/drivers/usb/mon/mon_text.c index 1f715436d6d3..a7eb4c99342c 100644 --- a/drivers/usb/mon/mon_text.c +++ b/drivers/usb/mon/mon_text.c @@ -733,7 +733,7 @@ int __init mon_text_init(void) { struct dentry *mondir; - mondir = debugfs_create_dir("usbmon", NULL); + mondir = debugfs_create_dir("usbmon", usb_debug_root); if (IS_ERR(mondir)) { printk(KERN_NOTICE TAG ": debugfs is not available\n"); return -ENODEV; -- cgit v1.2.3 From 97d7b7a41bd462abceee7dbb2b3afacfd52438ed Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Fri, 24 Apr 2009 15:16:04 -0700 Subject: USB: add the usbfs devices file to debugfs People are very used to the devices file in usbfs. Now that we have moved usbfs to be an "embedded" option only, the developers miss the file, they had grown quite attached to it over all of these years. This patch brings it back and puts it in the usb debugfs directory, so that the developers don't feel sad anymore. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/Makefile | 4 ++-- drivers/usb/core/usb.c | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index b6078706fb93..ec16e6029905 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -4,14 +4,14 @@ usbcore-objs := usb.o hub.o hcd.o urb.o message.o driver.o \ config.o file.o buffer.o sysfs.o endpoint.o \ - devio.o notify.o generic.o quirks.o + devio.o notify.o generic.o quirks.o devices.o ifeq ($(CONFIG_PCI),y) usbcore-objs += hcd-pci.o endif ifeq ($(CONFIG_USB_DEVICEFS),y) - usbcore-objs += inode.o devices.o + usbcore-objs += inode.o endif obj-$(CONFIG_USB) += usbcore.o diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 5f6873f5f268..c71590666ade 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1005,16 +1005,29 @@ static struct notifier_block usb_bus_nb = { struct dentry *usb_debug_root; EXPORT_SYMBOL_GPL(usb_debug_root); +struct dentry *usb_debug_devices; + static int usb_debugfs_init(void) { usb_debug_root = debugfs_create_dir("usb", NULL); if (!usb_debug_root) return -ENOENT; + + usb_debug_devices = debugfs_create_file("devices", 0444, + usb_debug_root, NULL, + &usbfs_devices_fops); + if (!usb_debug_devices) { + debugfs_remove(usb_debug_root); + usb_debug_root = NULL; + return -ENOENT; + } + return 0; } static void usb_debugfs_cleanup(void) { + debugfs_remove(usb_debug_devices); debugfs_remove(usb_debug_root); } -- cgit v1.2.3 From a853a3d4eb2edb066248a39f0634f6f5858816a0 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch <clemens@ladisch.de> Date: Fri, 24 Apr 2009 10:12:18 +0200 Subject: usb: return device strings in UTF-8 Change the encoding of strings returned by usb_string() from ISO 8859-1 to UTF-8. Signed-off-by: Clemens Ladisch <clemens@ladisch.de> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/Kconfig | 1 + drivers/usb/core/message.c | 41 +++++++++++++++++------------------------ 2 files changed, 18 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 5eee3f82be5d..dcd49f1e96d0 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -64,6 +64,7 @@ config USB_ARCH_HAS_EHCI config USB tristate "Support for Host-side USB" depends on USB_ARCH_HAS_HCD + select NLS # for UTF-8 strings ---help--- Universal Serial Bus (USB) is a specification for a serial bus subsystem which offers higher speeds and more features than the diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index b62628377654..e98f928c08ea 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -10,6 +10,7 @@ #include <linux/mm.h> #include <linux/timer.h> #include <linux/ctype.h> +#include <linux/nls.h> #include <linux/device.h> #include <linux/scatterlist.h> #include <linux/usb/quirks.h> @@ -759,7 +760,7 @@ static int usb_string_sub(struct usb_device *dev, unsigned int langid, } /** - * usb_string - returns ISO 8859-1 version of a string descriptor + * usb_string - returns UTF-8 version of a string descriptor * @dev: the device whose string descriptor is being retrieved * @index: the number of the descriptor * @buf: where to put the string @@ -767,17 +768,10 @@ static int usb_string_sub(struct usb_device *dev, unsigned int langid, * Context: !in_interrupt () * * This converts the UTF-16LE encoded strings returned by devices, from - * usb_get_string_descriptor(), to null-terminated ISO-8859-1 encoded ones - * that are more usable in most kernel contexts. Note that all characters - * in the chosen descriptor that can't be encoded using ISO-8859-1 - * are converted to the question mark ("?") character, and this function + * usb_get_string_descriptor(), to null-terminated UTF-8 encoded ones + * that are more usable in most kernel contexts. Note that this function * chooses strings in the first language supported by the device. * - * The ASCII (or, redundantly, "US-ASCII") character set is the seven-bit - * subset of ISO 8859-1. ISO-8859-1 is the eight-bit subset of Unicode, - * and is appropriate for use many uses of English and several other - * Western European languages. (But it doesn't include the "Euro" symbol.) - * * This call is synchronous, and may not be used in an interrupt context. * * Returns length of the string (>= 0) or usb_control_msg status (< 0). @@ -786,14 +780,14 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) { unsigned char *tbuf; int err; - unsigned int u, idx; + unsigned int u; if (dev->state == USB_STATE_SUSPENDED) return -EHOSTUNREACH; if (size <= 0 || !buf || !index) return -EINVAL; buf[0] = 0; - tbuf = kmalloc(256, GFP_NOIO); + tbuf = kmalloc(256 + 2, GFP_NOIO); if (!tbuf) return -ENOMEM; @@ -820,17 +814,13 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) if (err < 0) goto errout; + for (u = 2; u < err; u += 2) + le16_to_cpus((u16 *)&tbuf[u]); + tbuf[u] = 0; + tbuf[u + 1] = 0; size--; /* leave room for trailing NULL char in output buffer */ - for (idx = 0, u = 2; u < err; u += 2) { - if (idx >= size) - break; - if (tbuf[u+1]) /* high byte */ - buf[idx++] = '?'; /* non ISO-8859-1 character */ - else - buf[idx++] = tbuf[u]; - } - buf[idx] = 0; - err = idx; + err = utf8_wcstombs(buf, (u16 *)&tbuf[2], size); + buf[err] = 0; if (tbuf[1] != USB_DT_STRING) dev_dbg(&dev->dev, @@ -843,6 +833,9 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) } EXPORT_SYMBOL_GPL(usb_string); +/* one UTF-8-encoded 16-bit character has at most three bytes */ +#define MAX_USB_STRING_SIZE (127 * 3 + 1) + /** * usb_cache_string - read a string descriptor and cache it for later use * @udev: the device whose string descriptor is being read @@ -860,9 +853,9 @@ char *usb_cache_string(struct usb_device *udev, int index) if (index <= 0) return NULL; - buf = kmalloc(256, GFP_KERNEL); + buf = kmalloc(MAX_USB_STRING_SIZE, GFP_KERNEL); if (buf) { - len = usb_string(udev, index, buf, 256); + len = usb_string(udev, index, buf, MAX_USB_STRING_SIZE); if (len > 0) { smallbuf = kmalloc(++len, GFP_KERNEL); if (!smallbuf) -- cgit v1.2.3 From 74675a58507e769beee7d949dbed788af3c4139d Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Thu, 30 Apr 2009 10:08:18 -0400 Subject: NLS: update handling of Unicode This patch (as1239) updates the kernel's treatment of Unicode. The character-set conversion routines are well behind the current state of the Unicode specification: They don't recognize the existence of code points beyond plane 0 or of surrogate pairs in the UTF-16 encoding. The old wchar_t 16-bit type is retained because it's still used in lots of places. This shouldn't cause any new problems; if a conversion now results in an invalid 16-bit code then before it must have yielded an undefined code. Difficult-to-read names like "utf_mbstowcs" are replaced with more transparent names like "utf8s_to_utf16s" and the ordering of the parameters is rationalized (buffer lengths come immediate after the pointers they refer to, and the inputs precede the outputs). Fortunately the low-level conversion routines are used in only a few places; the interfaces to the higher-level uni2char and char2uni methods have been left unchanged. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Acked-by: Clemens Ladisch <clemens@ladisch.de> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/message.c | 10 +-- fs/befs/linuxvfs.c | 20 +++--- fs/fat/dir.c | 29 ++++---- fs/fat/namei_vfat.c | 4 +- fs/isofs/joliet.c | 36 +--------- fs/ncpfs/ncplib_kernel.c | 8 ++- fs/nls/nls_base.c | 164 +++++++++++++++++++++++++++++---------------- fs/nls/nls_utf8.c | 13 +++- include/linux/nls.h | 35 ++++++++-- 9 files changed, 182 insertions(+), 137 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index e98f928c08ea..9bd26dec7599 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -780,14 +780,13 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) { unsigned char *tbuf; int err; - unsigned int u; if (dev->state == USB_STATE_SUSPENDED) return -EHOSTUNREACH; if (size <= 0 || !buf || !index) return -EINVAL; buf[0] = 0; - tbuf = kmalloc(256 + 2, GFP_NOIO); + tbuf = kmalloc(256, GFP_NOIO); if (!tbuf) return -ENOMEM; @@ -814,12 +813,9 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) if (err < 0) goto errout; - for (u = 2; u < err; u += 2) - le16_to_cpus((u16 *)&tbuf[u]); - tbuf[u] = 0; - tbuf[u + 1] = 0; size--; /* leave room for trailing NULL char in output buffer */ - err = utf8_wcstombs(buf, (u16 *)&tbuf[2], size); + err = utf16s_to_utf8s((wchar_t *) &tbuf[2], (err - 2) / 2, + UTF16_LITTLE_ENDIAN, buf, size); buf[err] = 0; if (tbuf[1] != USB_DT_STRING) diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c index 9367b6297d84..89cd2deeb4af 100644 --- a/fs/befs/linuxvfs.c +++ b/fs/befs/linuxvfs.c @@ -513,7 +513,7 @@ befs_utf2nls(struct super_block *sb, const char *in, { struct nls_table *nls = BEFS_SB(sb)->nls; int i, o; - wchar_t uni; + unicode_t uni; int unilen, utflen; char *result; /* The utf8->nls conversion won't make the final nls string bigger @@ -539,16 +539,16 @@ befs_utf2nls(struct super_block *sb, const char *in, for (i = o = 0; i < in_len; i += utflen, o += unilen) { /* convert from UTF-8 to Unicode */ - utflen = utf8_mbtowc(&uni, &in[i], in_len - i); - if (utflen < 0) { + utflen = utf8_to_utf32(&in[i], in_len - i, &uni); + if (utflen < 0) goto conv_err; - } /* convert from Unicode to nls */ + if (uni > MAX_WCHAR_T) + goto conv_err; unilen = nls->uni2char(uni, &result[o], in_len - o); - if (unilen < 0) { + if (unilen < 0) goto conv_err; - } } result[o] = '\0'; *out_len = o; @@ -619,15 +619,13 @@ befs_nls2utf(struct super_block *sb, const char *in, /* convert from nls to unicode */ unilen = nls->char2uni(&in[i], in_len - i, &uni); - if (unilen < 0) { + if (unilen < 0) goto conv_err; - } /* convert from unicode to UTF-8 */ - utflen = utf8_wctomb(&result[o], uni, 3); - if (utflen <= 0) { + utflen = utf32_to_utf8(uni, &result[o], 3); + if (utflen <= 0) goto conv_err; - } } result[o] = '\0'; diff --git a/fs/fat/dir.c b/fs/fat/dir.c index f3500294eec5..7c14c8cbbaba 100644 --- a/fs/fat/dir.c +++ b/fs/fat/dir.c @@ -22,6 +22,19 @@ #include <asm/uaccess.h> #include "fat.h" +/* + * Maximum buffer size of short name. + * [(MSDOS_NAME + '.') * max one char + nul] + * For msdos style, ['.' (hidden) + MSDOS_NAME + '.' + nul] + */ +#define FAT_MAX_SHORT_SIZE ((MSDOS_NAME + 1) * NLS_MAX_CHARSET_SIZE + 1) +/* + * Maximum buffer size of unicode chars from slots. + * [(max longname slots * 13 (size in a slot) + nul) * sizeof(wchar_t)] + */ +#define FAT_MAX_UNI_CHARS ((MSDOS_SLOTS - 1) * 13 + 1) +#define FAT_MAX_UNI_SIZE (FAT_MAX_UNI_CHARS * sizeof(wchar_t)) + static inline loff_t fat_make_i_pos(struct super_block *sb, struct buffer_head *bh, struct msdos_dir_entry *de) @@ -171,7 +184,8 @@ static inline int fat_uni_to_x8(struct msdos_sb_info *sbi, const wchar_t *uni, unsigned char *buf, int size) { if (sbi->options.utf8) - return utf8_wcstombs(buf, uni, size); + return utf16s_to_utf8s(uni, FAT_MAX_UNI_CHARS, + UTF16_HOST_ENDIAN, buf, size); else return uni16_to_x8(buf, uni, size, sbi->options.unicode_xlate, sbi->nls_io); @@ -324,19 +338,6 @@ parse_long: return 0; } -/* - * Maximum buffer size of short name. - * [(MSDOS_NAME + '.') * max one char + nul] - * For msdos style, ['.' (hidden) + MSDOS_NAME + '.' + nul] - */ -#define FAT_MAX_SHORT_SIZE ((MSDOS_NAME + 1) * NLS_MAX_CHARSET_SIZE + 1) -/* - * Maximum buffer size of unicode chars from slots. - * [(max longname slots * 13 (size in a slot) + nul) * sizeof(wchar_t)] - */ -#define FAT_MAX_UNI_CHARS ((MSDOS_SLOTS - 1) * 13 + 1) -#define FAT_MAX_UNI_SIZE (FAT_MAX_UNI_CHARS * sizeof(wchar_t)) - /* * Return values: negative -> error, 0 -> not found, positive -> found, * value is the total amount of slots, including the shortname entry. diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index b50ecbe97f83..f92ad9995356 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -502,11 +502,11 @@ xlate_to_uni(const unsigned char *name, int len, unsigned char *outname, if (utf8) { int name_len = strlen(name); - *outlen = utf8_mbstowcs((wchar_t *)outname, name, PATH_MAX); + *outlen = utf8s_to_utf16s(name, PATH_MAX, (wchar_t *) outname); /* * We stripped '.'s before and set len appropriately, - * but utf8_mbstowcs doesn't care about len + * but utf8s_to_utf16s doesn't care about len */ *outlen -= (name_len - len); diff --git a/fs/isofs/joliet.c b/fs/isofs/joliet.c index 92c14b850e9c..a048de81c093 100644 --- a/fs/isofs/joliet.c +++ b/fs/isofs/joliet.c @@ -37,37 +37,6 @@ uni16_to_x8(unsigned char *ascii, __be16 *uni, int len, struct nls_table *nls) return (op - ascii); } -/* Convert big endian wide character string to utf8 */ -static int -wcsntombs_be(__u8 *s, const __u8 *pwcs, int inlen, int maxlen) -{ - const __u8 *ip; - __u8 *op; - int size; - __u16 c; - - op = s; - ip = pwcs; - while ((*ip || ip[1]) && (maxlen > 0) && (inlen > 0)) { - c = (*ip << 8) | ip[1]; - if (c > 0x7f) { - size = utf8_wctomb(op, c, maxlen); - if (size == -1) { - /* Ignore character and move on */ - maxlen--; - } else { - op += size; - maxlen -= size; - } - } else { - *op++ = (__u8) c; - } - ip += 2; - inlen--; - } - return (op - s); -} - int get_joliet_filename(struct iso_directory_record * de, unsigned char *outname, struct inode * inode) { @@ -79,8 +48,9 @@ get_joliet_filename(struct iso_directory_record * de, unsigned char *outname, st nls = ISOFS_SB(inode->i_sb)->s_nls_iocharset; if (utf8) { - len = wcsntombs_be(outname, de->name, - de->name_len[0] >> 1, PAGE_SIZE); + len = utf16s_to_utf8s((const wchar_t *) de->name, + de->name_len[0] >> 1, UTF16_BIG_ENDIAN, + outname, PAGE_SIZE); } else { len = uni16_to_x8(outname, (__be16 *) de->name, de->name_len[0] >> 1, nls); diff --git a/fs/ncpfs/ncplib_kernel.c b/fs/ncpfs/ncplib_kernel.c index 97645f112114..0ec6237a5970 100644 --- a/fs/ncpfs/ncplib_kernel.c +++ b/fs/ncpfs/ncplib_kernel.c @@ -1113,11 +1113,13 @@ ncp__io2vol(struct ncp_server *server, unsigned char *vname, unsigned int *vlen, if (NCP_IS_FLAG(server, NCP_FLAG_UTF8)) { int k; + unicode_t u; - k = utf8_mbtowc(&ec, iname, iname_end - iname); - if (k < 0) + k = utf8_to_utf32(iname, iname_end - iname, &u); + if (k < 0 || u > MAX_WCHAR_T) return -EINVAL; iname += k; + ec = u; } else { if (*iname == NCP_ESC) { int k; @@ -1214,7 +1216,7 @@ ncp__vol2io(struct ncp_server *server, unsigned char *iname, unsigned int *ilen, if (NCP_IS_FLAG(server, NCP_FLAG_UTF8)) { int k; - k = utf8_wctomb(iname, ec, iname_end - iname); + k = utf32_to_utf8(ec, iname, iname_end - iname); if (k < 0) { err = -ENAMETOOLONG; goto quit; diff --git a/fs/nls/nls_base.c b/fs/nls/nls_base.c index 750abf211e26..477d37d83b31 100644 --- a/fs/nls/nls_base.c +++ b/fs/nls/nls_base.c @@ -15,6 +15,7 @@ #include <linux/errno.h> #include <linux/kmod.h> #include <linux/spinlock.h> +#include <asm/byteorder.h> static struct nls_table default_table; static struct nls_table *tables = &default_table; @@ -43,10 +44,17 @@ static const struct utf8_table utf8_table[] = {0, /* end of table */} }; -int -utf8_mbtowc(wchar_t *p, const __u8 *s, int n) +#define UNICODE_MAX 0x0010ffff +#define PLANE_SIZE 0x00010000 + +#define SURROGATE_MASK 0xfffff800 +#define SURROGATE_PAIR 0x0000d800 +#define SURROGATE_LOW 0x00000400 +#define SURROGATE_BITS 0x000003ff + +int utf8_to_utf32(const u8 *s, int len, unicode_t *pu) { - long l; + unsigned long l; int c0, c, nc; const struct utf8_table *t; @@ -57,12 +65,13 @@ utf8_mbtowc(wchar_t *p, const __u8 *s, int n) nc++; if ((c0 & t->cmask) == t->cval) { l &= t->lmask; - if (l < t->lval) + if (l < t->lval || l > UNICODE_MAX || + (l & SURROGATE_MASK) == SURROGATE_PAIR) return -1; - *p = l; + *pu = (unicode_t) l; return nc; } - if (n <= nc) + if (len <= nc) return -1; s++; c = (*s ^ 0x80) & 0xFF; @@ -72,76 +81,119 @@ utf8_mbtowc(wchar_t *p, const __u8 *s, int n) } return -1; } +EXPORT_SYMBOL(utf8_to_utf32); -int -utf8_mbstowcs(wchar_t *pwcs, const __u8 *s, int n) +int utf32_to_utf8(unicode_t u, u8 *s, int maxlen) { - __u16 *op; - const __u8 *ip; - int size; - - op = pwcs; - ip = s; - while (*ip && n > 0) { - if (*ip & 0x80) { - size = utf8_mbtowc(op, ip, n); - if (size == -1) { - /* Ignore character and move on */ - ip++; - n--; - } else { - op++; - ip += size; - n -= size; - } - } else { - *op++ = *ip++; - n--; - } - } - return (op - pwcs); -} - -int -utf8_wctomb(__u8 *s, wchar_t wc, int maxlen) -{ - long l; + unsigned long l; int c, nc; const struct utf8_table *t; - + if (!s) return 0; - - l = wc; + + l = u; + if (l > UNICODE_MAX || (l & SURROGATE_MASK) == SURROGATE_PAIR) + return -1; + nc = 0; for (t = utf8_table; t->cmask && maxlen; t++, maxlen--) { nc++; if (l <= t->lmask) { c = t->shift; - *s = t->cval | (l >> c); + *s = (u8) (t->cval | (l >> c)); while (c > 0) { c -= 6; s++; - *s = 0x80 | ((l >> c) & 0x3F); + *s = (u8) (0x80 | ((l >> c) & 0x3F)); } return nc; } } return -1; } +EXPORT_SYMBOL(utf32_to_utf8); -int -utf8_wcstombs(__u8 *s, const wchar_t *pwcs, int maxlen) +int utf8s_to_utf16s(const u8 *s, int len, wchar_t *pwcs) { - const __u16 *ip; - __u8 *op; + u16 *op; int size; + unicode_t u; + + op = pwcs; + while (*s && len > 0) { + if (*s & 0x80) { + size = utf8_to_utf32(s, len, &u); + if (size < 0) { + /* Ignore character and move on */ + size = 1; + } else if (u >= PLANE_SIZE) { + u -= PLANE_SIZE; + *op++ = (wchar_t) (SURROGATE_PAIR | + ((u >> 10) & SURROGATE_BITS)); + *op++ = (wchar_t) (SURROGATE_PAIR | + SURROGATE_LOW | + (u & SURROGATE_BITS)); + } else { + *op++ = (wchar_t) u; + } + s += size; + len -= size; + } else { + *op++ = *s++; + len--; + } + } + return op - pwcs; +} +EXPORT_SYMBOL(utf8s_to_utf16s); + +static inline unsigned long get_utf16(unsigned c, enum utf16_endian endian) +{ + switch (endian) { + default: + return c; + case UTF16_LITTLE_ENDIAN: + return __le16_to_cpu(c); + case UTF16_BIG_ENDIAN: + return __be16_to_cpu(c); + } +} + +int utf16s_to_utf8s(const wchar_t *pwcs, int len, enum utf16_endian endian, + u8 *s, int maxlen) +{ + u8 *op; + int size; + unsigned long u, v; op = s; - ip = pwcs; - while (*ip && maxlen > 0) { - if (*ip > 0x7f) { - size = utf8_wctomb(op, *ip, maxlen); + while (len > 0 && maxlen > 0) { + u = get_utf16(*pwcs, endian); + if (!u) + break; + pwcs++; + len--; + if (u > 0x7f) { + if ((u & SURROGATE_MASK) == SURROGATE_PAIR) { + if (u & SURROGATE_LOW) { + /* Ignore character and move on */ + continue; + } + if (len <= 0) + break; + v = get_utf16(*pwcs, endian); + if ((v & SURROGATE_MASK) != SURROGATE_PAIR || + !(v & SURROGATE_LOW)) { + /* Ignore character and move on */ + continue; + } + u = PLANE_SIZE + ((u & SURROGATE_BITS) << 10) + + (v & SURROGATE_BITS); + pwcs++; + len--; + } + size = utf32_to_utf8(u, op, maxlen); if (size == -1) { /* Ignore character and move on */ } else { @@ -149,13 +201,13 @@ utf8_wcstombs(__u8 *s, const wchar_t *pwcs, int maxlen) maxlen -= size; } } else { - *op++ = (__u8) *ip; + *op++ = (u8) u; maxlen--; } - ip++; } - return (op - s); + return op - s; } +EXPORT_SYMBOL(utf16s_to_utf8s); int register_nls(struct nls_table * nls) { @@ -467,9 +519,5 @@ EXPORT_SYMBOL(unregister_nls); EXPORT_SYMBOL(unload_nls); EXPORT_SYMBOL(load_nls); EXPORT_SYMBOL(load_nls_default); -EXPORT_SYMBOL(utf8_mbtowc); -EXPORT_SYMBOL(utf8_mbstowcs); -EXPORT_SYMBOL(utf8_wctomb); -EXPORT_SYMBOL(utf8_wcstombs); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/fs/nls/nls_utf8.c b/fs/nls/nls_utf8.c index aa2c42fdd977..0d60a44acacd 100644 --- a/fs/nls/nls_utf8.c +++ b/fs/nls/nls_utf8.c @@ -15,7 +15,11 @@ static int uni2char(wchar_t uni, unsigned char *out, int boundlen) { int n; - if ( (n = utf8_wctomb(out, uni, boundlen)) == -1) { + if (boundlen <= 0) + return -ENAMETOOLONG; + + n = utf32_to_utf8(uni, out, boundlen); + if (n < 0) { *out = '?'; return -EINVAL; } @@ -25,11 +29,14 @@ static int uni2char(wchar_t uni, unsigned char *out, int boundlen) static int char2uni(const unsigned char *rawstring, int boundlen, wchar_t *uni) { int n; + unicode_t u; - if ( (n = utf8_mbtowc(uni, rawstring, boundlen)) == -1) { + n = utf8_to_utf32(rawstring, boundlen, &u); + if (n < 0 || u > MAX_WCHAR_T) { *uni = 0x003f; /* ? */ - n = -EINVAL; + return -EINVAL; } + *uni = (wchar_t) u; return n; } diff --git a/include/linux/nls.h b/include/linux/nls.h index 52b1a76c1b43..d47beef08dfd 100644 --- a/include/linux/nls.h +++ b/include/linux/nls.h @@ -3,8 +3,23 @@ #include <linux/init.h> -/* unicode character */ -typedef __u16 wchar_t; +/* Unicode has changed over the years. Unicode code points no longer + * fit into 16 bits; as of Unicode 5 valid code points range from 0 + * to 0x10ffff (17 planes, where each plane holds 65536 code points). + * + * The original decision to represent Unicode characters as 16-bit + * wchar_t values is now outdated. But plane 0 still includes the + * most commonly used characters, so we will retain it. The newer + * 32-bit unicode_t type can be used when it is necessary to + * represent the full Unicode character set. + */ + +/* Plane-0 Unicode character */ +typedef u16 wchar_t; +#define MAX_WCHAR_T 0xffff + +/* Arbitrary Unicode character */ +typedef u32 unicode_t; struct nls_table { const char *charset; @@ -21,6 +36,13 @@ struct nls_table { /* this value hold the maximum octet of charset */ #define NLS_MAX_CHARSET_SIZE 6 /* for UTF-8 */ +/* Byte order for UTF-16 strings */ +enum utf16_endian { + UTF16_HOST_ENDIAN, + UTF16_LITTLE_ENDIAN, + UTF16_BIG_ENDIAN +}; + /* nls.c */ extern int register_nls(struct nls_table *); extern int unregister_nls(struct nls_table *); @@ -28,10 +50,11 @@ extern struct nls_table *load_nls(char *); extern void unload_nls(struct nls_table *); extern struct nls_table *load_nls_default(void); -extern int utf8_mbtowc(wchar_t *, const __u8 *, int); -extern int utf8_mbstowcs(wchar_t *, const __u8 *, int); -extern int utf8_wctomb(__u8 *, wchar_t, int); -extern int utf8_wcstombs(__u8 *, const wchar_t *, int); +extern int utf8_to_utf32(const u8 *s, int len, unicode_t *pu); +extern int utf32_to_utf8(unicode_t u, u8 *s, int maxlen); +extern int utf8s_to_utf16s(const u8 *s, int len, wchar_t *pwcs); +extern int utf16s_to_utf8s(const wchar_t *pwcs, int len, + enum utf16_endian endian, u8 *s, int maxlen); static inline unsigned char nls_tolower(struct nls_table *t, unsigned char c) { -- cgit v1.2.3 From 109833417cd0bf27089fcca5b0d6a44c8d592452 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Mon, 27 Apr 2009 13:12:58 -0700 Subject: USB: OHCI: use the ohci structure directly in debugfs files. Right now we jump through some hoops to get to the struct ohci_hcd struct in the ohci debugfs files. Remove all of the fun casting around and just use the pointer directly. This is needed as the dev pointer in the hcd structure is going away, and it makes the code simpler and smaller Cc: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ohci-dbg.c | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c index d3269656aa4d..811f5dfdc582 100644 --- a/drivers/usb/host/ohci-dbg.c +++ b/drivers/usb/host/ohci-dbg.c @@ -431,7 +431,7 @@ static struct dentry *ohci_debug_root; struct debug_buffer { ssize_t (*fill_func)(struct debug_buffer *); /* fill method */ - struct device *dev; + struct ohci_hcd *ohci; struct mutex mutex; /* protect filling of buffer */ size_t count; /* number of characters filled into buffer */ char *page; @@ -505,15 +505,11 @@ show_list (struct ohci_hcd *ohci, char *buf, size_t count, struct ed *ed) static ssize_t fill_async_buffer(struct debug_buffer *buf) { - struct usb_bus *bus; - struct usb_hcd *hcd; struct ohci_hcd *ohci; size_t temp; unsigned long flags; - bus = dev_get_drvdata(buf->dev); - hcd = bus_to_hcd(bus); - ohci = hcd_to_ohci(hcd); + ohci = buf->ohci; /* display control and bulk lists together, for simplicity */ spin_lock_irqsave (&ohci->lock, flags); @@ -529,8 +525,6 @@ static ssize_t fill_async_buffer(struct debug_buffer *buf) static ssize_t fill_periodic_buffer(struct debug_buffer *buf) { - struct usb_bus *bus; - struct usb_hcd *hcd; struct ohci_hcd *ohci; struct ed **seen, *ed; unsigned long flags; @@ -542,9 +536,7 @@ static ssize_t fill_periodic_buffer(struct debug_buffer *buf) return 0; seen_count = 0; - bus = (struct usb_bus *)dev_get_drvdata(buf->dev); - hcd = bus_to_hcd(bus); - ohci = hcd_to_ohci(hcd); + ohci = buf->ohci; next = buf->page; size = PAGE_SIZE; @@ -626,7 +618,6 @@ static ssize_t fill_periodic_buffer(struct debug_buffer *buf) static ssize_t fill_registers_buffer(struct debug_buffer *buf) { - struct usb_bus *bus; struct usb_hcd *hcd; struct ohci_hcd *ohci; struct ohci_regs __iomem *regs; @@ -635,9 +626,8 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf) char *next; u32 rdata; - bus = (struct usb_bus *)dev_get_drvdata(buf->dev); - hcd = bus_to_hcd(bus); - ohci = hcd_to_ohci(hcd); + ohci = buf->ohci; + hcd = ohci_to_hcd(ohci); regs = ohci->regs; next = buf->page; size = PAGE_SIZE; @@ -710,7 +700,7 @@ done: return PAGE_SIZE - size; } -static struct debug_buffer *alloc_buffer(struct device *dev, +static struct debug_buffer *alloc_buffer(struct ohci_hcd *ohci, ssize_t (*fill_func)(struct debug_buffer *)) { struct debug_buffer *buf; @@ -718,7 +708,7 @@ static struct debug_buffer *alloc_buffer(struct device *dev, buf = kzalloc(sizeof(struct debug_buffer), GFP_KERNEL); if (buf) { - buf->dev = dev; + buf->ohci = ohci; buf->fill_func = fill_func; mutex_init(&buf->mutex); } @@ -810,26 +800,25 @@ static int debug_registers_open(struct inode *inode, struct file *file) static inline void create_debug_files (struct ohci_hcd *ohci) { struct usb_bus *bus = &ohci_to_hcd(ohci)->self; - struct device *dev = bus->dev; ohci->debug_dir = debugfs_create_dir(bus->bus_name, ohci_debug_root); if (!ohci->debug_dir) goto dir_error; ohci->debug_async = debugfs_create_file("async", S_IRUGO, - ohci->debug_dir, dev, + ohci->debug_dir, ohci, &debug_async_fops); if (!ohci->debug_async) goto async_error; ohci->debug_periodic = debugfs_create_file("periodic", S_IRUGO, - ohci->debug_dir, dev, + ohci->debug_dir, ohci, &debug_periodic_fops); if (!ohci->debug_periodic) goto periodic_error; ohci->debug_registers = debugfs_create_file("registers", S_IRUGO, - ohci->debug_dir, dev, + ohci->debug_dir, ohci, &debug_registers_fops); if (!ohci->debug_registers) goto registers_error; -- cgit v1.2.3 From ed14f0340a4954c1a9ffaff01c261428b5753e9d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Mon, 27 Apr 2009 13:15:38 -0700 Subject: USB: EHCI: create sysfs companion files directly in the controller device The controller device is where we want this sysfs file, especially as the dev pointer is about to go away... Cc: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-hub.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 97a53a48a3d8..f46ad27c9a90 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -391,7 +391,7 @@ static inline void create_companion_file(struct ehci_hcd *ehci) /* with integrated TT there is no companion! */ if (!ehci_is_TDI(ehci)) - i = device_create_file(ehci_to_hcd(ehci)->self.dev, + i = device_create_file(ehci_to_hcd(ehci)->self.controller, &dev_attr_companion); } @@ -399,7 +399,7 @@ static inline void remove_companion_file(struct ehci_hcd *ehci) { /* with integrated TT there is no companion! */ if (!ehci_is_TDI(ehci)) - device_remove_file(ehci_to_hcd(ehci)->self.dev, + device_remove_file(ehci_to_hcd(ehci)->self.controller, &dev_attr_companion); } -- cgit v1.2.3 From 820d7a253c5e59a786d5b608f6e8d0419fdc2f6e Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Mon, 27 Apr 2009 13:17:21 -0700 Subject: USB: remove unused usb_host class The usb_host class isn't used for anything anymore (it was used for debug files, but they have moved to debugfs a few kernel releases ago), so let's delete it before someone accidentally puts a file in it. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hcd.c | 27 --------------------------- drivers/usb/core/usb.c | 6 ------ drivers/usb/core/usb.h | 2 -- include/linux/usb.h | 1 - 4 files changed, 36 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 42b93da1085d..c65d95604028 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -755,23 +755,6 @@ static struct attribute_group usb_bus_attr_group = { /*-------------------------------------------------------------------------*/ -static struct class *usb_host_class; - -int usb_host_init(void) -{ - int retval = 0; - - usb_host_class = class_create(THIS_MODULE, "usb_host"); - if (IS_ERR(usb_host_class)) - retval = PTR_ERR(usb_host_class); - return retval; -} - -void usb_host_cleanup(void) -{ - class_destroy(usb_host_class); -} - /** * usb_bus_init - shared initialization code * @bus: the bus structure being initialized @@ -818,12 +801,6 @@ static int usb_register_bus(struct usb_bus *bus) set_bit (busnum, busmap.busmap); bus->busnum = busnum; - bus->dev = device_create(usb_host_class, bus->controller, MKDEV(0, 0), - bus, "usb_host%d", busnum); - result = PTR_ERR(bus->dev); - if (IS_ERR(bus->dev)) - goto error_create_class_dev; - /* Add it to the local list of buses */ list_add (&bus->bus_list, &usb_bus_list); mutex_unlock(&usb_bus_list_lock); @@ -834,8 +811,6 @@ static int usb_register_bus(struct usb_bus *bus) "number %d\n", bus->busnum); return 0; -error_create_class_dev: - clear_bit(busnum, busmap.busmap); error_find_busnum: mutex_unlock(&usb_bus_list_lock); return result; @@ -865,8 +840,6 @@ static void usb_deregister_bus (struct usb_bus *bus) usb_notify_remove_bus(bus); clear_bit (bus->busnum, busmap.busmap); - - device_unregister(bus->dev); } /** diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index c71590666ade..eb810bbe7bbc 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -1055,9 +1055,6 @@ static int __init usb_init(void) retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb); if (retval) goto bus_notifier_failed; - retval = usb_host_init(); - if (retval) - goto host_init_failed; retval = usb_major_init(); if (retval) goto major_init_failed; @@ -1087,8 +1084,6 @@ usb_devio_init_failed: driver_register_failed: usb_major_cleanup(); major_init_failed: - usb_host_cleanup(); -host_init_failed: bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); bus_notifier_failed: bus_unregister(&usb_bus_type); @@ -1113,7 +1108,6 @@ static void __exit usb_exit(void) usb_deregister(&usbfs_driver); usb_devio_cleanup(); usb_hub_cleanup(); - usb_host_cleanup(); bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); bus_unregister(&usb_bus_type); ksuspend_usb_cleanup(); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 79d8a9ea559b..dabf9255a10e 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -41,8 +41,6 @@ extern int usb_hub_init(void); extern void usb_hub_cleanup(void); extern int usb_major_init(void); extern void usb_major_cleanup(void); -extern int usb_host_init(void); -extern void usb_host_cleanup(void); #ifdef CONFIG_PM diff --git a/include/linux/usb.h b/include/linux/usb.h index 29060dad81e6..606e0aa5bbe6 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -336,7 +336,6 @@ struct usb_bus { #ifdef CONFIG_USB_DEVICEFS struct dentry *usbfs_dentry; /* usbfs dentry entry for the bus */ #endif - struct device *dev; /* device for this bus */ #if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE) struct mon_bus *mon_bus; /* non-null when associated */ -- cgit v1.2.3 From b748bb71b50fcea991e5c17bb3e10b5f38d21eaa Mon Sep 17 00:00:00 2001 From: Elina Pasheva <epasheva@sierrawireless.com> Date: Fri, 24 Apr 2009 18:41:49 -0700 Subject: USB: serial: sierra driver performance improvements - Version number set to 1.3.4 - Increased the number of input/output URBs for improved performance (numbers based on an measurement study triggered by a user request). We performed the testing using a network simulator that provided full speeds in the uplink and downlink directions and this combination of URBs provided the best throughput. Signed-off-by: Elina Pasheva <epasheva@sierrawireless.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/sierra.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 17ac34f4d668..9826c85a812d 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -14,7 +14,7 @@ Whom based his on the Keyspan driver by Hugh Blemings <hugh@blemings.org> */ -#define DRIVER_VERSION "v.1.3.3" +#define DRIVER_VERSION "v.1.3.4" #define DRIVER_AUTHOR "Kevin Lloyd <klloyd@sierrawireless.com>" #define DRIVER_DESC "USB Driver for Sierra Wireless USB modems" @@ -30,8 +30,8 @@ #define SWIMS_USB_REQUEST_SetPower 0x00 #define SWIMS_USB_REQUEST_SetNmea 0x07 -#define N_IN_URB 4 -#define N_OUT_URB 4 +#define N_IN_URB 8 +#define N_OUT_URB 64 #define IN_BUFLEN 4096 static int debug; -- cgit v1.2.3 From 5d44b36120d5b67081419d9307a526a0dfd949fc Mon Sep 17 00:00:00 2001 From: Elina Pasheva <epasheva@sierrawireless.com> Date: Mon, 27 Apr 2009 18:41:52 -0700 Subject: USB: serial: sierra driver debug info visibility improvement - Version number set to 1.3.5 - Added "\n" at the end of each string in dev_dbg() code to improve the debug information visibility. Without this change the debug logs are very difficult to read. Signed-off-by: Elina Pasheva <epasheva@sierrawireless.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/sierra.c | 50 ++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 9826c85a812d..f75688416b65 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -14,7 +14,7 @@ Whom based his on the Keyspan driver by Hugh Blemings <hugh@blemings.org> */ -#define DRIVER_VERSION "v.1.3.4" +#define DRIVER_VERSION "v.1.3.5" #define DRIVER_AUTHOR "Kevin Lloyd <klloyd@sierrawireless.com>" #define DRIVER_DESC "USB Driver for Sierra Wireless USB modems" @@ -46,7 +46,7 @@ struct sierra_iface_info { static int sierra_set_power_state(struct usb_device *udev, __u16 swiState) { int result; - dev_dbg(&udev->dev, "%s", __func__); + dev_dbg(&udev->dev, "%s\n", __func__); result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), SWIMS_USB_REQUEST_SetPower, /* __u8 request */ USB_TYPE_VENDOR, /* __u8 request type */ @@ -61,7 +61,7 @@ static int sierra_set_power_state(struct usb_device *udev, __u16 swiState) static int sierra_vsc_set_nmea(struct usb_device *udev, __u16 enable) { int result; - dev_dbg(&udev->dev, "%s", __func__); + dev_dbg(&udev->dev, "%s\n", __func__); result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), SWIMS_USB_REQUEST_SetNmea, /* __u8 request */ USB_TYPE_VENDOR, /* __u8 request type */ @@ -77,7 +77,7 @@ static int sierra_calc_num_ports(struct usb_serial *serial) { int result; int *num_ports = usb_get_serial_data(serial); - dev_dbg(&serial->dev->dev, "%s", __func__); + dev_dbg(&serial->dev->dev, "%s\n", __func__); result = *num_ports; @@ -111,7 +111,7 @@ static int sierra_calc_interface(struct usb_serial *serial) int interface; struct usb_interface *p_interface; struct usb_host_interface *p_host_interface; - dev_dbg(&serial->dev->dev, "%s", __func__); + dev_dbg(&serial->dev->dev, "%s\n", __func__); /* Get the interface structure pointer from the serial struct */ p_interface = serial->interface; @@ -136,7 +136,7 @@ static int sierra_probe(struct usb_serial *serial, u8 ifnum; u8 numendpoints; - dev_dbg(&serial->dev->dev, "%s", __func__); + dev_dbg(&serial->dev->dev, "%s\n", __func__); num_ports = kmalloc(sizeof(*num_ports), GFP_KERNEL); if (!num_ports) @@ -289,7 +289,7 @@ static int sierra_send_setup(struct usb_serial_port *port) __u16 interface = 0; int val = 0; - dev_dbg(&port->dev, "%s", __func__); + dev_dbg(&port->dev, "%s\n", __func__); portdata = usb_get_serial_port_data(port); @@ -332,7 +332,7 @@ static int sierra_send_setup(struct usb_serial_port *port) static void sierra_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { - dev_dbg(&port->dev, "%s", __func__); + dev_dbg(&port->dev, "%s\n", __func__); tty_termios_copy_hw(tty->termios, old_termios); sierra_send_setup(port); } @@ -343,7 +343,7 @@ static int sierra_tiocmget(struct tty_struct *tty, struct file *file) unsigned int value; struct sierra_port_private *portdata; - dev_dbg(&port->dev, "%s", __func__); + dev_dbg(&port->dev, "%s\n", __func__); portdata = usb_get_serial_port_data(port); value = ((portdata->rts_state) ? TIOCM_RTS : 0) | @@ -394,14 +394,14 @@ static void sierra_outdat_callback(struct urb *urb) int status = urb->status; unsigned long flags; - dev_dbg(&port->dev, "%s - port %d", __func__, port->number); + dev_dbg(&port->dev, "%s - port %d\n", __func__, port->number); /* free up the transfer buffer, as usb_free_urb() does not do this */ kfree(urb->transfer_buffer); if (status) dev_dbg(&port->dev, "%s - nonzero write bulk status " - "received: %d", __func__, status); + "received: %d\n", __func__, status); spin_lock_irqsave(&portdata->lock, flags); --portdata->outstanding_urbs; @@ -423,7 +423,7 @@ static int sierra_write(struct tty_struct *tty, struct usb_serial_port *port, portdata = usb_get_serial_port_data(port); - dev_dbg(&port->dev, "%s: write (%d chars)", __func__, count); + dev_dbg(&port->dev, "%s: write (%d chars)\n", __func__, count); spin_lock_irqsave(&portdata->lock, flags); if (portdata->outstanding_urbs > N_OUT_URB) { @@ -498,7 +498,7 @@ static void sierra_indat_callback(struct urb *urb) if (status) { dev_dbg(&port->dev, "%s: nonzero status: %d on" - " endpoint %02x.", __func__, status, endpoint); + " endpoint %02x\n", __func__, status, endpoint); } else { if (urb->actual_length) { tty = tty_port_tty_get(&port->port); @@ -508,7 +508,7 @@ static void sierra_indat_callback(struct urb *urb) tty_kref_put(tty); } else dev_dbg(&port->dev, "%s: empty read urb" - " received", __func__); + " received\n", __func__); /* Resubmit urb so we continue receiving */ if (port->port.count && status != -ESHUTDOWN && status != -EPERM) { @@ -529,8 +529,8 @@ static void sierra_instat_callback(struct urb *urb) struct sierra_port_private *portdata = usb_get_serial_port_data(port); struct usb_serial *serial = port->serial; - dev_dbg(&port->dev, "%s", __func__); - dev_dbg(&port->dev, "%s: urb %p port %p has data %p", __func__, + dev_dbg(&port->dev, "%s\n", __func__); + dev_dbg(&port->dev, "%s: urb %p port %p has data %p\n", __func__, urb, port, portdata); if (status == 0) { @@ -550,7 +550,7 @@ static void sierra_instat_callback(struct urb *urb) sizeof(struct usb_ctrlrequest)); struct tty_struct *tty; - dev_dbg(&port->dev, "%s: signal x%x", __func__, + dev_dbg(&port->dev, "%s: signal x%x\n", __func__, signals); old_dcd_state = portdata->dcd_state; @@ -565,12 +565,12 @@ static void sierra_instat_callback(struct urb *urb) tty_hangup(tty); tty_kref_put(tty); } else { - dev_dbg(&port->dev, "%s: type %x req %x", + dev_dbg(&port->dev, "%s: type %x req %x\n", __func__, req_pkt->bRequestType, req_pkt->bRequest); } } else - dev_dbg(&port->dev, "%s: error %d", __func__, status); + dev_dbg(&port->dev, "%s: error %d\n", __func__, status); /* Resubmit urb so we continue receiving IRQ data */ if (status != -ESHUTDOWN) { @@ -578,7 +578,7 @@ static void sierra_instat_callback(struct urb *urb) err = usb_submit_urb(urb, GFP_ATOMIC); if (err) dev_dbg(&port->dev, "%s: resubmit intr urb " - "failed. (%d)", __func__, err); + "failed. (%d)\n", __func__, err); } } @@ -588,7 +588,7 @@ static int sierra_write_room(struct tty_struct *tty) struct sierra_port_private *portdata = usb_get_serial_port_data(port); unsigned long flags; - dev_dbg(&port->dev, "%s - port %d", __func__, port->number); + dev_dbg(&port->dev, "%s - port %d\n", __func__, port->number); /* try to give a good number back based on if we have any free urbs at * this point in time */ @@ -729,7 +729,7 @@ static int sierra_open(struct tty_struct *tty, portdata = usb_get_serial_port_data(port); - dev_dbg(&port->dev, "%s", __func__); + dev_dbg(&port->dev, "%s\n", __func__); /* Set some sane defaults */ portdata->rts_state = 1; @@ -782,7 +782,7 @@ static int sierra_startup(struct usb_serial *serial) struct sierra_port_private *portdata; int i; - dev_dbg(&serial->dev->dev, "%s", __func__); + dev_dbg(&serial->dev->dev, "%s\n", __func__); /* Set Device mode to D0 */ sierra_set_power_state(serial->dev, 0x0000); @@ -797,7 +797,7 @@ static int sierra_startup(struct usb_serial *serial) portdata = kzalloc(sizeof(*portdata), GFP_KERNEL); if (!portdata) { dev_dbg(&port->dev, "%s: kmalloc for " - "sierra_port_private (%d) failed!.", + "sierra_port_private (%d) failed!.\n", __func__, i); return -ENOMEM; } @@ -815,7 +815,7 @@ static void sierra_shutdown(struct usb_serial *serial) struct usb_serial_port *port; struct sierra_port_private *portdata; - dev_dbg(&serial->dev->dev, "%s", __func__); + dev_dbg(&serial->dev->dev, "%s\n", __func__); for (i = 0; i < serial->num_ports; ++i) { port = serial->port[i]; -- cgit v1.2.3 From 604eb89ffed9fba268582dc44d5b462ea94cc0ca Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Mon, 27 Apr 2009 13:19:41 -0400 Subject: USB: g_file_storage: use the "unaligned" accessors This patch (as1233) makes g_file_storage use the "unaligned" accessors. This is based on work originally done by Harvey Harrison. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Acked-by: Harvey Harrison <harvey.harrison@gmail.com> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/file_storage.c | 93 ++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 51 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c index 381a53b3e11c..1e6aa504d58a 100644 --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -248,6 +248,8 @@ #include <linux/freezer.h> #include <linux/utsname.h> +#include <asm/unaligned.h> + #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> @@ -799,29 +801,9 @@ static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) /* Routines for unaligned data access */ -static u16 get_be16(u8 *buf) -{ - return ((u16) buf[0] << 8) | ((u16) buf[1]); -} - -static u32 get_be32(u8 *buf) -{ - return ((u32) buf[0] << 24) | ((u32) buf[1] << 16) | - ((u32) buf[2] << 8) | ((u32) buf[3]); -} - -static void put_be16(u8 *buf, u16 val) -{ - buf[0] = val >> 8; - buf[1] = val; -} - -static void put_be32(u8 *buf, u32 val) +static u32 get_unaligned_be24(u8 *buf) { - buf[0] = val >> 24; - buf[1] = val >> 16; - buf[2] = val >> 8; - buf[3] = val & 0xff; + return 0xffffff & (u32) get_unaligned_be32(buf - 1); } @@ -1582,9 +1564,9 @@ static int do_read(struct fsg_dev *fsg) /* Get the starting Logical Block Address and check that it's * not too big */ if (fsg->cmnd[0] == SC_READ_6) - lba = (fsg->cmnd[1] << 16) | get_be16(&fsg->cmnd[2]); + lba = get_unaligned_be24(&fsg->cmnd[1]); else { - lba = get_be32(&fsg->cmnd[2]); + lba = get_unaligned_be32(&fsg->cmnd[2]); /* We allow DPO (Disable Page Out = don't save data in the * cache) and FUA (Force Unit Access = don't read from the @@ -1717,9 +1699,9 @@ static int do_write(struct fsg_dev *fsg) /* Get the starting Logical Block Address and check that it's * not too big */ if (fsg->cmnd[0] == SC_WRITE_6) - lba = (fsg->cmnd[1] << 16) | get_be16(&fsg->cmnd[2]); + lba = get_unaligned_be24(&fsg->cmnd[1]); else { - lba = get_be32(&fsg->cmnd[2]); + lba = get_unaligned_be32(&fsg->cmnd[2]); /* We allow DPO (Disable Page Out = don't save data in the * cache) and FUA (Force Unit Access = write directly to the @@ -1940,7 +1922,7 @@ static int do_verify(struct fsg_dev *fsg) /* Get the starting Logical Block Address and check that it's * not too big */ - lba = get_be32(&fsg->cmnd[2]); + lba = get_unaligned_be32(&fsg->cmnd[2]); if (lba >= curlun->num_sectors) { curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; return -EINVAL; @@ -1953,7 +1935,7 @@ static int do_verify(struct fsg_dev *fsg) return -EINVAL; } - verification_length = get_be16(&fsg->cmnd[7]); + verification_length = get_unaligned_be16(&fsg->cmnd[7]); if (unlikely(verification_length == 0)) return -EIO; // No default reply @@ -2103,7 +2085,7 @@ static int do_request_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) memset(buf, 0, 18); buf[0] = valid | 0x70; // Valid, current error buf[2] = SK(sd); - put_be32(&buf[3], sdinfo); // Sense information + put_unaligned_be32(sdinfo, &buf[3]); /* Sense information */ buf[7] = 18 - 8; // Additional sense length buf[12] = ASC(sd); buf[13] = ASCQ(sd); @@ -2114,7 +2096,7 @@ static int do_request_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) static int do_read_capacity(struct fsg_dev *fsg, struct fsg_buffhd *bh) { struct lun *curlun = fsg->curlun; - u32 lba = get_be32(&fsg->cmnd[2]); + u32 lba = get_unaligned_be32(&fsg->cmnd[2]); int pmi = fsg->cmnd[8]; u8 *buf = (u8 *) bh->buf; @@ -2124,8 +2106,9 @@ static int do_read_capacity(struct fsg_dev *fsg, struct fsg_buffhd *bh) return -EINVAL; } - put_be32(&buf[0], curlun->num_sectors - 1); // Max logical block - put_be32(&buf[4], 512); // Block length + put_unaligned_be32(curlun->num_sectors - 1, &buf[0]); + /* Max logical block */ + put_unaligned_be32(512, &buf[4]); /* Block length */ return 8; } @@ -2144,7 +2127,7 @@ static void store_cdrom_address(u8 *dest, int msf, u32 addr) dest[0] = 0; /* Reserved */ } else { /* Absolute sector */ - put_be32(dest, addr); + put_unaligned_be32(addr, dest); } } @@ -2152,7 +2135,7 @@ static int do_read_header(struct fsg_dev *fsg, struct fsg_buffhd *bh) { struct lun *curlun = fsg->curlun; int msf = fsg->cmnd[1] & 0x02; - u32 lba = get_be32(&fsg->cmnd[2]); + u32 lba = get_unaligned_be32(&fsg->cmnd[2]); u8 *buf = (u8 *) bh->buf; if ((fsg->cmnd[1] & ~0x02) != 0) { /* Mask away MSF */ @@ -2252,10 +2235,13 @@ static int do_mode_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) buf[2] = 0x04; // Write cache enable, // Read cache not disabled // No cache retention priorities - put_be16(&buf[4], 0xffff); // Don't disable prefetch - // Minimum prefetch = 0 - put_be16(&buf[8], 0xffff); // Maximum prefetch - put_be16(&buf[10], 0xffff); // Maximum prefetch ceiling + put_unaligned_be16(0xffff, &buf[4]); + /* Don't disable prefetch */ + /* Minimum prefetch = 0 */ + put_unaligned_be16(0xffff, &buf[8]); + /* Maximum prefetch */ + put_unaligned_be16(0xffff, &buf[10]); + /* Maximum prefetch ceiling */ } buf += 12; } @@ -2272,7 +2258,7 @@ static int do_mode_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) if (mscmnd == SC_MODE_SENSE_6) buf0[0] = len - 1; else - put_be16(buf0, len - 2); + put_unaligned_be16(len - 2, buf0); return len; } @@ -2360,9 +2346,10 @@ static int do_read_format_capacities(struct fsg_dev *fsg, buf[3] = 8; // Only the Current/Maximum Capacity Descriptor buf += 4; - put_be32(&buf[0], curlun->num_sectors); // Number of blocks - put_be32(&buf[4], 512); // Block length - buf[4] = 0x02; // Current capacity + put_unaligned_be32(curlun->num_sectors, &buf[0]); + /* Number of blocks */ + put_unaligned_be32(512, &buf[4]); /* Block length */ + buf[4] = 0x02; /* Current capacity */ return 12; } @@ -2882,7 +2869,7 @@ static int do_scsi_command(struct fsg_dev *fsg) break; case SC_MODE_SELECT_10: - fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST, (1<<1) | (3<<7), 0, "MODE SELECT(10)")) == 0) @@ -2898,7 +2885,7 @@ static int do_scsi_command(struct fsg_dev *fsg) break; case SC_MODE_SENSE_10: - fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, (1<<1) | (1<<2) | (3<<7), 0, "MODE SENSE(10)")) == 0) @@ -2923,7 +2910,8 @@ static int do_scsi_command(struct fsg_dev *fsg) break; case SC_READ_10: - fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]) << 9; + fsg->data_size_from_cmnd = + get_unaligned_be16(&fsg->cmnd[7]) << 9; if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, (1<<1) | (0xf<<2) | (3<<7), 1, "READ(10)")) == 0) @@ -2931,7 +2919,8 @@ static int do_scsi_command(struct fsg_dev *fsg) break; case SC_READ_12: - fsg->data_size_from_cmnd = get_be32(&fsg->cmnd[6]) << 9; + fsg->data_size_from_cmnd = + get_unaligned_be32(&fsg->cmnd[6]) << 9; if ((reply = check_command(fsg, 12, DATA_DIR_TO_HOST, (1<<1) | (0xf<<2) | (0xf<<6), 1, "READ(12)")) == 0) @@ -2949,7 +2938,7 @@ static int do_scsi_command(struct fsg_dev *fsg) case SC_READ_HEADER: if (!mod_data.cdrom) goto unknown_cmnd; - fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, (3<<7) | (0x1f<<1), 1, "READ HEADER")) == 0) @@ -2959,7 +2948,7 @@ static int do_scsi_command(struct fsg_dev *fsg) case SC_READ_TOC: if (!mod_data.cdrom) goto unknown_cmnd; - fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, (7<<6) | (1<<1), 1, "READ TOC")) == 0) @@ -2967,7 +2956,7 @@ static int do_scsi_command(struct fsg_dev *fsg) break; case SC_READ_FORMAT_CAPACITIES: - fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, (3<<7), 1, "READ FORMAT CAPACITIES")) == 0) @@ -3025,7 +3014,8 @@ static int do_scsi_command(struct fsg_dev *fsg) break; case SC_WRITE_10: - fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]) << 9; + fsg->data_size_from_cmnd = + get_unaligned_be16(&fsg->cmnd[7]) << 9; if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST, (1<<1) | (0xf<<2) | (3<<7), 1, "WRITE(10)")) == 0) @@ -3033,7 +3023,8 @@ static int do_scsi_command(struct fsg_dev *fsg) break; case SC_WRITE_12: - fsg->data_size_from_cmnd = get_be32(&fsg->cmnd[6]) << 9; + fsg->data_size_from_cmnd = + get_unaligned_be32(&fsg->cmnd[6]) << 9; if ((reply = check_command(fsg, 12, DATA_DIR_FROM_HOST, (1<<1) | (0xf<<2) | (0xf<<6), 1, "WRITE(12)")) == 0) -- cgit v1.2.3 From 00240c3839d843ccf07abd52806f421f7b87bbdc Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Mon, 27 Apr 2009 13:33:16 -0400 Subject: PCI: add power-state name strings This patch (as1235) adds an array of PCI power-state names, together with a simple inline accessor routine. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Acked-by: Rafael J. Wysocki <rjw@sisk.pl> Acked-by: Jesse Barnes <jbarnes@virtuousgeek.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/pci/pci.c | 5 +++++ include/linux/pci.h | 8 ++++++++ 2 files changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 1a91bf9687af..07bbb9b3b93f 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -24,6 +24,11 @@ #include <asm/setup.h> #include "pci.h" +const char *pci_power_names[] = { + "error", "D0", "D1", "D2", "D3hot", "D3cold", "unknown", +}; +EXPORT_SYMBOL_GPL(pci_power_names); + unsigned int pci_pm_d3_delay = PCI_PM_D3_WAIT; #ifdef CONFIG_PCI_DOMAINS diff --git a/include/linux/pci.h b/include/linux/pci.h index 72698d89e767..8e366bb0705f 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -124,6 +124,14 @@ typedef int __bitwise pci_power_t; #define PCI_UNKNOWN ((pci_power_t __force) 5) #define PCI_POWER_ERROR ((pci_power_t __force) -1) +/* Remember to update this when the list above changes! */ +extern const char *pci_power_names[]; + +static inline const char *pci_power_name(pci_power_t state) +{ + return pci_power_names[1 + (int) state]; +} + #define PCI_PM_D2_DELAY 200 #define PCI_PM_D3_WAIT 10 #define PCI_PM_BUS_WAIT 50 -- cgit v1.2.3 From abb306416a7ec2386678de0da6b632a6cb068af0 Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Mon, 27 Apr 2009 13:33:24 -0400 Subject: USB: move PCI host controllers to new PM framework This patch (as1236) converts the USB PCI power management routines over to the new PM framework. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Acked-by: Rafael J. Wysocki <rjw@sisk.pl> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hcd-pci.c | 242 ++++++++++++++++++++++---------------------- drivers/usb/core/hcd.h | 9 +- drivers/usb/host/ehci-pci.c | 9 +- drivers/usb/host/ohci-pci.c | 11 +- drivers/usb/host/uhci-hcd.c | 9 +- 5 files changed, 141 insertions(+), 139 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index a4301dc02d27..5db4d40db832 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -185,194 +185,198 @@ void usb_hcd_pci_remove(struct pci_dev *dev) } EXPORT_SYMBOL_GPL(usb_hcd_pci_remove); - -#ifdef CONFIG_PM - /** - * usb_hcd_pci_suspend - power management suspend of a PCI-based HCD - * @dev: USB Host Controller being suspended - * @message: Power Management message describing this state transition - * - * Store this function in the HCD's struct pci_driver as .suspend. + * usb_hcd_pci_shutdown - shutdown host controller + * @dev: USB Host Controller being shutdown */ -int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) +void usb_hcd_pci_shutdown(struct pci_dev *dev) +{ + struct usb_hcd *hcd; + + hcd = pci_get_drvdata(dev); + if (!hcd) + return; + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} +EXPORT_SYMBOL_GPL(usb_hcd_pci_shutdown); + +#ifdef CONFIG_PM_SLEEP + +static int check_root_hub_suspended(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + + if (!(hcd->state == HC_STATE_SUSPENDED || + hcd->state == HC_STATE_HALT)) { + dev_warn(dev, "Root hub is not suspended\n"); + return -EBUSY; + } + return 0; +} + +static int hcd_pci_suspend(struct device *dev) { - struct usb_hcd *hcd = pci_get_drvdata(dev); - int retval = 0; - int wake, w; - int has_pci_pm; + struct pci_dev *pci_dev = to_pci_dev(dev); + struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + int retval; /* Root hub suspend should have stopped all downstream traffic, * and all bus master traffic. And done so for both the interface * and the stub usb_device (which we check here). But maybe it * didn't; writing sysfs power/state files ignores such rules... - * - * We must ignore the FREEZE vs SUSPEND distinction here, because - * otherwise the swsusp will save (and restore) garbage state. */ - if (!(hcd->state == HC_STATE_SUSPENDED || - hcd->state == HC_STATE_HALT)) { - dev_warn(&dev->dev, "Root hub is not suspended\n"); - retval = -EBUSY; - goto done; - } + retval = check_root_hub_suspended(dev); + if (retval) + return retval; /* We might already be suspended (runtime PM -- not yet written) */ - if (dev->current_state != PCI_D0) - goto done; + if (pci_dev->current_state != PCI_D0) + return retval; if (hcd->driver->pci_suspend) { - retval = hcd->driver->pci_suspend(hcd, message); + retval = hcd->driver->pci_suspend(hcd, PMSG_SUSPEND); suspend_report_result(hcd->driver->pci_suspend, retval); if (retval) - goto done; + return retval; } - synchronize_irq(dev->irq); + synchronize_irq(pci_dev->irq); /* Downstream ports from this root hub should already be quiesced, so * there will be no DMA activity. Now we can shut down the upstream - * link (except maybe for PME# resume signaling) and enter some PCI - * low power state, if the hardware allows. + * link (except maybe for PME# resume signaling). We'll enter a + * low power state during suspend_noirq, if the hardware allows. */ - pci_disable_device(dev); + pci_disable_device(pci_dev); + return retval; +} + +static int hcd_pci_suspend_noirq(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + int retval; + + retval = check_root_hub_suspended(dev); + if (retval) + return retval; - pci_save_state(dev); + pci_save_state(pci_dev); - /* Don't fail on error to enable wakeup. We rely on pci code - * to reject requests the hardware can't implement, rather - * than coding the same thing. + /* If the root hub is HALTed rather than SUSPENDed, + * disallow remote wakeup. */ - wake = (hcd->state == HC_STATE_SUSPENDED && - device_may_wakeup(&dev->dev)); - w = pci_wake_from_d3(dev, wake); - if (w < 0) - wake = w; - dev_dbg(&dev->dev, "wakeup: %d\n", wake); - - /* Don't change state if we don't need to */ - if (message.event == PM_EVENT_FREEZE || - message.event == PM_EVENT_PRETHAW) { - dev_dbg(&dev->dev, "--> no state change\n"); - goto done; - } + if (hcd->state == HC_STATE_HALT) + device_set_wakeup_enable(dev, 0); + dev_dbg(dev, "wakeup: %d\n", device_may_wakeup(dev)); - has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM); - if (!has_pci_pm) { - dev_dbg(&dev->dev, "--> PCI D0 legacy\n"); + /* Possibly enable remote wakeup, + * choose the appropriate low-power state, and go to that state. + */ + retval = pci_prepare_to_sleep(pci_dev); + if (retval == -EIO) { /* Low-power not supported */ + dev_dbg(dev, "--> PCI D0 legacy\n"); + retval = 0; + } else if (retval == 0) { + dev_dbg(dev, "--> PCI %s\n", + pci_power_name(pci_dev->current_state)); } else { - - /* NOTE: dev->current_state becomes nonzero only here, and - * only for devices that support PCI PM. Also, exiting - * PCI_D3 (but not PCI_D1 or PCI_D2) is allowed to reset - * some device state (e.g. as part of clock reinit). - */ - retval = pci_set_power_state(dev, PCI_D3hot); - suspend_report_result(pci_set_power_state, retval); - if (retval == 0) { - dev_dbg(&dev->dev, "--> PCI D3\n"); - } else { - dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n", - retval); - pci_restore_state(dev); - } + suspend_report_result(pci_prepare_to_sleep, retval); + return retval; } #ifdef CONFIG_PPC_PMAC - if (retval == 0) { - /* Disable ASIC clocks for USB */ - if (machine_is(powermac)) { - struct device_node *of_node; - - of_node = pci_device_to_OF_node(dev); - if (of_node) - pmac_call_feature(PMAC_FTR_USB_ENABLE, - of_node, 0, 0); - } + /* Disable ASIC clocks for USB */ + if (machine_is(powermac)) { + struct device_node *of_node; + + of_node = pci_device_to_OF_node(pci_dev); + if (of_node) + pmac_call_feature(PMAC_FTR_USB_ENABLE, of_node, 0, 0); } #endif - - done: return retval; } -EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); -/** - * usb_hcd_pci_resume - power management resume of a PCI-based HCD - * @dev: USB Host Controller being resumed - * - * Store this function in the HCD's struct pci_driver as .resume. - */ -int usb_hcd_pci_resume(struct pci_dev *dev) +static int hcd_pci_resume_noirq(struct device *dev) { - struct usb_hcd *hcd; - int retval; + struct pci_dev *pci_dev = to_pci_dev(dev); #ifdef CONFIG_PPC_PMAC /* Reenable ASIC clocks for USB */ if (machine_is(powermac)) { struct device_node *of_node; - of_node = pci_device_to_OF_node(dev); + of_node = pci_device_to_OF_node(pci_dev); if (of_node) pmac_call_feature(PMAC_FTR_USB_ENABLE, of_node, 0, 1); } #endif - pci_restore_state(dev); + /* Go back to D0 and disable remote wakeup */ + pci_back_from_sleep(pci_dev); + return 0; +} + +static int resume_common(struct device *dev, bool hibernated) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + int retval; - hcd = pci_get_drvdata(dev); if (hcd->state != HC_STATE_SUSPENDED) { - dev_dbg(hcd->self.controller, - "can't resume, not suspended!\n"); + dev_dbg(dev, "can't resume, not suspended!\n"); return 0; } - pci_enable_wake(dev, PCI_D0, false); - - retval = pci_enable_device(dev); + retval = pci_enable_device(pci_dev); if (retval < 0) { - dev_err(&dev->dev, "can't re-enable after resume, %d!\n", - retval); + dev_err(dev, "can't re-enable after resume, %d!\n", retval); return retval; } - pci_set_master(dev); - - /* yes, ignore this result too... */ - (void) pci_wake_from_d3(dev, 0); + pci_set_master(pci_dev); clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); if (hcd->driver->pci_resume) { retval = hcd->driver->pci_resume(hcd); if (retval) { - dev_err(hcd->self.controller, - "PCI post-resume error %d!\n", retval); + dev_err(dev, "PCI post-resume error %d!\n", retval); usb_hc_died(hcd); } } return retval; } -EXPORT_SYMBOL_GPL(usb_hcd_pci_resume); -#endif /* CONFIG_PM */ - -/** - * usb_hcd_pci_shutdown - shutdown host controller - * @dev: USB Host Controller being shutdown - */ -void usb_hcd_pci_shutdown(struct pci_dev *dev) +static int hcd_pci_resume(struct device *dev) { - struct usb_hcd *hcd; - - hcd = pci_get_drvdata(dev); - if (!hcd) - return; + return resume_common(dev, false); +} - if (hcd->driver->shutdown) - hcd->driver->shutdown(hcd); +static int hcd_pci_restore(struct device *dev) +{ + return resume_common(dev, true); } -EXPORT_SYMBOL_GPL(usb_hcd_pci_shutdown); +struct dev_pm_ops usb_hcd_pci_pm_ops = { + .suspend = hcd_pci_suspend, + .suspend_noirq = hcd_pci_suspend_noirq, + .resume_noirq = hcd_pci_resume_noirq, + .resume = hcd_pci_resume, + .freeze = check_root_hub_suspended, + .freeze_noirq = check_root_hub_suspended, + .thaw_noirq = NULL, + .thaw = NULL, + .poweroff = hcd_pci_suspend, + .poweroff_noirq = hcd_pci_suspend_noirq, + .restore_noirq = hcd_pci_resume_noirq, + .restore = hcd_pci_restore, +}; +EXPORT_SYMBOL_GPL(usb_hcd_pci_pm_ops); + +#endif /* CONFIG_PM_SLEEP */ diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index e7d4479de41c..7f068d6e6940 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -261,14 +261,11 @@ struct pci_device_id; extern int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id); extern void usb_hcd_pci_remove(struct pci_dev *dev); - -#ifdef CONFIG_PM -extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t msg); -extern int usb_hcd_pci_resume(struct pci_dev *dev); -#endif /* CONFIG_PM */ - extern void usb_hcd_pci_shutdown(struct pci_dev *dev); +#ifdef CONFIG_PM_SLEEP +extern struct dev_pm_ops usb_hcd_pci_pm_ops; +#endif #endif /* CONFIG_PCI */ /* pci-ish (pdev null is ok) buffer alloc/mapping support */ diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 5aa8bce90e1f..8172383e8908 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -429,10 +429,11 @@ static struct pci_driver ehci_pci_driver = { .probe = usb_hcd_pci_probe, .remove = usb_hcd_pci_remove, + .shutdown = usb_hcd_pci_shutdown, -#ifdef CONFIG_PM - .suspend = usb_hcd_pci_suspend, - .resume = usb_hcd_pci_resume, +#ifdef CONFIG_PM_SLEEP + .driver = { + .pm = &usb_hcd_pci_pm_ops + }, #endif - .shutdown = usb_hcd_pci_shutdown, }; diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index f9961b4c0da3..ee0a68ca5fda 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -484,12 +484,11 @@ static struct pci_driver ohci_pci_driver = { .probe = usb_hcd_pci_probe, .remove = usb_hcd_pci_remove, + .shutdown = usb_hcd_pci_shutdown, -#ifdef CONFIG_PM - .suspend = usb_hcd_pci_suspend, - .resume = usb_hcd_pci_resume, +#ifdef CONFIG_PM_SLEEP + .driver = { + .pm = &usb_hcd_pci_pm_ops + }, #endif - - .shutdown = usb_hcd_pci_shutdown, }; - diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index f2fd709fcce7..c0133211e3ec 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -940,10 +940,11 @@ static struct pci_driver uhci_pci_driver = { .remove = usb_hcd_pci_remove, .shutdown = uhci_shutdown, -#ifdef CONFIG_PM - .suspend = usb_hcd_pci_suspend, - .resume = usb_hcd_pci_resume, -#endif /* PM */ +#ifdef CONFIG_PM_SLEEP + .driver = { + .pm = &usb_hcd_pci_pm_ops + }, +#endif }; static int __init uhci_hcd_init(void) -- cgit v1.2.3 From 6ec4beb5c701f728548b587082c83ef62eb36035 Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Mon, 27 Apr 2009 13:33:41 -0400 Subject: USB: new flag for resume-from-hibernation This patch (as1237) changes the way the PCI host controller drivers avoid retaining bogus hardware states during resume-from-hibernation. Previously we had reset the hardware as part of preparing to reinstate the memory image. But we can do better now with the new PM framework, since we know exactly which resume operations are from hibernation. The pci_resume method is changed to accept a flag indicating whether the system is resuming from hibernation. When this flag is set, the drivers will reset the hardware to get rid of any existing state. Similarly, the pci_suspend method is changed to remove the pm_message_t argument. It's no longer needed, since no special action has to be taken when preparing to reinstate the memory image. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Acked-by: Rafael J. Wysocki <rjw@sisk.pl> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hcd-pci.c | 4 ++-- drivers/usb/core/hcd.h | 4 ++-- drivers/usb/host/ehci-pci.c | 17 ++++++----------- drivers/usb/host/ohci-pci.c | 13 +++++++------ drivers/usb/host/uhci-hcd.c | 12 ++++++------ 5 files changed, 23 insertions(+), 27 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 5db4d40db832..91f2885b6ee1 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -237,7 +237,7 @@ static int hcd_pci_suspend(struct device *dev) return retval; if (hcd->driver->pci_suspend) { - retval = hcd->driver->pci_suspend(hcd, PMSG_SUSPEND); + retval = hcd->driver->pci_suspend(hcd); suspend_report_result(hcd->driver->pci_suspend, retval); if (retval) return retval; @@ -344,7 +344,7 @@ static int resume_common(struct device *dev, bool hibernated) clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); if (hcd->driver->pci_resume) { - retval = hcd->driver->pci_resume(hcd); + retval = hcd->driver->pci_resume(hcd, hibernated); if (retval) { dev_err(dev, "PCI post-resume error %d!\n", retval); usb_hc_died(hcd); diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 7f068d6e6940..174170f14f06 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -182,10 +182,10 @@ struct hc_driver { * a whole, not just the root hub; they're for PCI bus glue. */ /* called after suspending the hub, before entering D3 etc */ - int (*pci_suspend) (struct usb_hcd *hcd, pm_message_t message); + int (*pci_suspend)(struct usb_hcd *hcd); /* called after entering D0 (etc), before resuming the hub */ - int (*pci_resume) (struct usb_hcd *hcd); + int (*pci_resume)(struct usb_hcd *hcd, bool hibernated); /* cleanly make HCD stop writing memory and doing I/O */ void (*stop) (struct usb_hcd *hcd); diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 8172383e8908..e74948898f76 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -268,7 +268,7 @@ done: * Also they depend on separate root hub suspend/resume. */ -static int ehci_pci_suspend(struct usb_hcd *hcd, pm_message_t message) +static int ehci_pci_suspend(struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); unsigned long flags; @@ -293,12 +293,6 @@ static int ehci_pci_suspend(struct usb_hcd *hcd, pm_message_t message) ehci_writel(ehci, 0, &ehci->regs->intr_enable); (void)ehci_readl(ehci, &ehci->regs->intr_enable); - /* make sure snapshot being resumed re-enumerates everything */ - if (message.event == PM_EVENT_PRETHAW) { - ehci_halt(ehci); - ehci_reset(ehci); - } - clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); bail: spin_unlock_irqrestore (&ehci->lock, flags); @@ -309,7 +303,7 @@ static int ehci_pci_suspend(struct usb_hcd *hcd, pm_message_t message) return rc; } -static int ehci_pci_resume(struct usb_hcd *hcd) +static int ehci_pci_resume(struct usb_hcd *hcd, bool hibernated) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); struct pci_dev *pdev = to_pci_dev(hcd->self.controller); @@ -322,10 +316,12 @@ static int ehci_pci_resume(struct usb_hcd *hcd) /* Mark hardware accessible again as we are out of D3 state by now */ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - /* If CF is still set, we maintained PCI Vaux power. + /* If CF is still set and we aren't resuming from hibernation + * then we maintained PCI Vaux power. * Just undo the effect of ehci_pci_suspend(). */ - if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) { + if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF && + !hibernated) { int mask = INTR_MASK; if (!hcd->self.root_hub->do_remote_wakeup) @@ -335,7 +331,6 @@ static int ehci_pci_resume(struct usb_hcd *hcd) return 0; } - ehci_dbg(ehci, "lost power, restarting\n"); usb_root_hub_lost_power(hcd->self.root_hub); /* Else reset, to cope with power loss or flush-to-storage diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index ee0a68ca5fda..d2ba04dd785e 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -372,7 +372,7 @@ static int __devinit ohci_pci_start (struct usb_hcd *hcd) #ifdef CONFIG_PM -static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message) +static int ohci_pci_suspend(struct usb_hcd *hcd) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); unsigned long flags; @@ -394,10 +394,6 @@ static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message) ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); (void)ohci_readl(ohci, &ohci->regs->intrdisable); - /* make sure snapshot being resumed re-enumerates everything */ - if (message.event == PM_EVENT_PRETHAW) - ohci_usb_reset(ohci); - clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); bail: spin_unlock_irqrestore (&ohci->lock, flags); @@ -406,9 +402,14 @@ static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message) } -static int ohci_pci_resume (struct usb_hcd *hcd) +static int ohci_pci_resume(struct usb_hcd *hcd, bool hibernated) { set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + /* Make sure resume from hibernation re-enumerates everything */ + if (hibernated) + ohci_usb_reset(hcd_to_ohci(hcd)); + ohci_finish_controller_resume(hcd); return 0; } diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index c0133211e3ec..274751b4409c 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -769,7 +769,7 @@ static int uhci_rh_resume(struct usb_hcd *hcd) return rc; } -static int uhci_pci_suspend(struct usb_hcd *hcd, pm_message_t message) +static int uhci_pci_suspend(struct usb_hcd *hcd) { struct uhci_hcd *uhci = hcd_to_uhci(hcd); int rc = 0; @@ -795,10 +795,6 @@ static int uhci_pci_suspend(struct usb_hcd *hcd, pm_message_t message) /* FIXME: Enable non-PME# remote wakeup? */ - /* make sure snapshot being resumed re-enumerates everything */ - if (message.event == PM_EVENT_PRETHAW) - uhci_hc_died(uhci); - done_okay: clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); done: @@ -806,7 +802,7 @@ done: return rc; } -static int uhci_pci_resume(struct usb_hcd *hcd) +static int uhci_pci_resume(struct usb_hcd *hcd, bool hibernated) { struct uhci_hcd *uhci = hcd_to_uhci(hcd); @@ -820,6 +816,10 @@ static int uhci_pci_resume(struct usb_hcd *hcd) spin_lock_irq(&uhci->lock); + /* Make sure resume from hibernation re-enumerates everything */ + if (hibernated) + uhci_hc_died(uhci); + /* FIXME: Disable non-PME# remote wakeup? */ /* The firmware or a boot kernel may have changed the controller -- cgit v1.2.3 From 9685a59a2bc0e1e85d1848c744214dbe74e9ce7d Mon Sep 17 00:00:00 2001 From: Richard Laager <rlaager@wiktel.com> Date: Sun, 26 Apr 2009 20:56:20 -0500 Subject: USB: Identify Novatel MC760 in option driver Identify the Novatel MC760/U760/USB760 in the option USB serial driver. Signed-off-by: Richard Laager <rlaager@wiktel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/option.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index f11672a25ead..a38971cc3731 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -204,9 +204,9 @@ static int option_resume(struct usb_serial *serial); #define NOVATELWIRELESS_PRODUCT_MC727 0x4100 #define NOVATELWIRELESS_PRODUCT_MC950D 0x4400 #define NOVATELWIRELESS_PRODUCT_U727 0x5010 +#define NOVATELWIRELESS_PRODUCT_MC760 0x6000 /* FUTURE NOVATEL PRODUCTS */ -#define NOVATELWIRELESS_PRODUCT_EVDO_FULLSPEED 0X6000 #define NOVATELWIRELESS_PRODUCT_EVDO_HIGHSPEED 0X6001 #define NOVATELWIRELESS_PRODUCT_HSPA_FULLSPEED 0X7000 #define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED 0X7001 @@ -428,7 +428,7 @@ static struct usb_device_id option_ids[] = { { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC950D) }, /* Novatel MC930D/MC950D */ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC727) }, /* Novatel MC727/U727/USB727 */ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U727) }, /* Novatel MC727/U727/USB727 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_FULLSPEED) }, /* Novatel EVDO product */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC760) }, /* Novatel MC760/U760/USB760 */ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_FULLSPEED) }, /* Novatel HSPA product */ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_FULLSPEED) }, /* Novatel EVDO Embedded product */ { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_FULLSPEED) }, /* Novatel HSPA Embedded product */ -- cgit v1.2.3 From b4072f46e57fe7138f4337840fab736ec43293ca Mon Sep 17 00:00:00 2001 From: Hermann Kneissel <hermann.kneissel@gmx.de> Date: Sun, 26 Apr 2009 22:42:04 +0200 Subject: USB: garmin_gps: removes usb_reset_device from garmin_close The following patch removes the call to usb_reset_device which may occur when closing the driver by implementing a new session initialization code based on the method used by gpsbabel. The patch is against linux-2.6.30-rc3-git1. Signed-off-by: Hermann Kneissel herkne@users.sourceforge.net Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/garmin_gps.c | 198 +++++++++++++++------------------------- 1 file changed, 73 insertions(+), 125 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c index ee25a3fe3b09..5092d6aba659 100644 --- a/drivers/usb/serial/garmin_gps.c +++ b/drivers/usb/serial/garmin_gps.c @@ -1,7 +1,7 @@ /* * Garmin GPS driver * - * Copyright (C) 2006,2007 Hermann Kneissel herkne@users.sourceforge.net + * Copyright (C) 2006-2009 Hermann Kneissel herkne@users.sourceforge.net * * The latest version of the driver can be found at * http://sourceforge.net/projects/garmin-gps/ @@ -51,7 +51,7 @@ static int debug; */ #define VERSION_MAJOR 0 -#define VERSION_MINOR 31 +#define VERSION_MINOR 33 #define _STR(s) #s #define _DRIVER_VERSION(a, b) "v" _STR(a) "." _STR(b) @@ -129,7 +129,6 @@ struct garmin_data { __u8 state; __u16 flags; __u8 mode; - __u8 ignorePkts; __u8 count; __u8 pkt_id; __u32 serial_num; @@ -141,8 +140,6 @@ struct garmin_data { __u8 inbuffer [GPS_IN_BUFSIZ]; /* tty -> usb */ __u8 outbuffer[GPS_OUT_BUFSIZ]; /* usb -> tty */ __u8 privpkt[4*6]; - atomic_t req_count; - atomic_t resp_count; spinlock_t lock; struct list_head pktlist; }; @@ -170,6 +167,8 @@ struct garmin_data { #define FLAGS_BULK_IN_ACTIVE 0x0020 #define FLAGS_BULK_IN_RESTART 0x0010 #define FLAGS_THROTTLED 0x0008 +#define APP_REQ_SEEN 0x0004 +#define APP_RESP_SEEN 0x0002 #define CLEAR_HALT_REQUIRED 0x0001 #define FLAGS_QUEUING 0x0100 @@ -184,20 +183,16 @@ struct garmin_data { /* function prototypes */ -static void gsp_next_packet(struct garmin_data *garmin_data_p); -static int garmin_write_bulk(struct usb_serial_port *port, +static int gsp_next_packet(struct garmin_data *garmin_data_p); +static int garmin_write_bulk(struct usb_serial_port *port, const unsigned char *buf, int count, int dismiss_ack); /* some special packets to be send or received */ static unsigned char const GARMIN_START_SESSION_REQ[] = { 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0 }; -static unsigned char const GARMIN_START_SESSION_REQ2[] - = { 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0 }; static unsigned char const GARMIN_START_SESSION_REPLY[] = { 0, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0 }; -static unsigned char const GARMIN_SESSION_ACTIVE_REPLY[] - = { 0, 0, 0, 0, 17, 0, 0, 0, 4, 0, 0, 0, 0, 16, 0, 0 }; static unsigned char const GARMIN_BULK_IN_AVAIL_REPLY[] = { 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0 }; static unsigned char const GARMIN_APP_LAYER_REPLY[] @@ -233,13 +228,6 @@ static struct usb_driver garmin_driver = { }; -static inline int noResponseFromAppLayer(struct garmin_data *garmin_data_p) -{ - return atomic_read(&garmin_data_p->req_count) == - atomic_read(&garmin_data_p->resp_count); -} - - static inline int getLayerId(const __u8 *usbPacket) { return __le32_to_cpup((__le32 *)(usbPacket)); @@ -325,8 +313,11 @@ static int pkt_add(struct garmin_data *garmin_data_p, state = garmin_data_p->state; spin_unlock_irqrestore(&garmin_data_p->lock, flags); + dbg("%s - added: pkt: %d - %d bytes", + __func__, pkt->seq, data_length); + /* in serial mode, if someone is waiting for data from - the device, iconvert and send the next packet to tty. */ + the device, convert and send the next packet to tty. */ if (result && (state == STATE_GSP_WAIT_DATA)) gsp_next_packet(garmin_data_p); } @@ -411,7 +402,7 @@ static int gsp_send_ack(struct garmin_data *garmin_data_p, __u8 pkt_id) /* * called for a complete packet received from tty layer * - * the complete packet (pkzid ... cksum) is in garmin_data_p->inbuf starting + * the complete packet (pktid ... cksum) is in garmin_data_p->inbuf starting * at GSP_INITIAL_OFFSET. * * count - number of bytes in the input buffer including space reserved for @@ -501,7 +492,6 @@ static int gsp_receive(struct garmin_data *garmin_data_p, unsigned long flags; int offs = 0; int ack_or_nak_seen = 0; - int i = 0; __u8 *dest; int size; /* dleSeen: set if last byte read was a DLE */ @@ -519,8 +509,8 @@ static int gsp_receive(struct garmin_data *garmin_data_p, skip = garmin_data_p->flags & FLAGS_GSP_SKIP; spin_unlock_irqrestore(&garmin_data_p->lock, flags); - dbg("%s - dle=%d skip=%d size=%d count=%d", - __func__, dleSeen, skip, size, count); + /* dbg("%s - dle=%d skip=%d size=%d count=%d", + __func__, dleSeen, skip, size, count); */ if (size == 0) size = GSP_INITIAL_OFFSET; @@ -568,7 +558,6 @@ static int gsp_receive(struct garmin_data *garmin_data_p, } else if (!skip) { if (dleSeen) { - dbg("non-masked DLE at %d - restarting", i); size = GSP_INITIAL_OFFSET; dleSeen = 0; } @@ -599,19 +588,19 @@ static int gsp_receive(struct garmin_data *garmin_data_p, else garmin_data_p->flags &= ~FLAGS_GSP_DLESEEN; - if (ack_or_nak_seen) - garmin_data_p->state = STATE_GSP_WAIT_DATA; - spin_unlock_irqrestore(&garmin_data_p->lock, flags); - if (ack_or_nak_seen) - gsp_next_packet(garmin_data_p); + if (ack_or_nak_seen) { + if (gsp_next_packet(garmin_data_p) > 0) + garmin_data_p->state = STATE_ACTIVE; + else + garmin_data_p->state = STATE_GSP_WAIT_DATA; + } return count; } - /* * Sends a usb packet to the tty * @@ -733,29 +722,28 @@ static int gsp_send(struct garmin_data *garmin_data_p, } - - - /* * Process the next pending data packet - if there is one */ -static void gsp_next_packet(struct garmin_data *garmin_data_p) +static int gsp_next_packet(struct garmin_data *garmin_data_p) { + int result = 0; struct garmin_packet *pkt = NULL; while ((pkt = pkt_pop(garmin_data_p)) != NULL) { dbg("%s - next pkt: %d", __func__, pkt->seq); - if (gsp_send(garmin_data_p, pkt->data, pkt->size) > 0) { + result = gsp_send(garmin_data_p, pkt->data, pkt->size); + if (result > 0) { kfree(pkt); - return; + return result; } kfree(pkt); } + return result; } - /****************************************************************************** * garmin native mode ******************************************************************************/ @@ -888,14 +876,6 @@ static int garmin_clear(struct garmin_data *garmin_data_p) unsigned long flags; int status = 0; - struct usb_serial_port *port = garmin_data_p->port; - - if (port != NULL && atomic_read(&garmin_data_p->resp_count)) { - /* send a terminate command */ - status = garmin_write_bulk(port, GARMIN_STOP_TRANSFER_REQ, - sizeof(GARMIN_STOP_TRANSFER_REQ), 1); - } - /* flush all queued data */ pkt_clear(garmin_data_p); @@ -908,16 +888,12 @@ static int garmin_clear(struct garmin_data *garmin_data_p) } - - - - static int garmin_init_session(struct usb_serial_port *port) { - unsigned long flags; struct usb_serial *serial = port->serial; struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); int status = 0; + int i = 0; if (status == 0) { usb_kill_urb(port->interrupt_in_urb); @@ -931,30 +907,25 @@ static int garmin_init_session(struct usb_serial_port *port) __func__, status); } + /* + * using the initialization method from gpsbabel. See comments in + * gpsbabel/jeeps/gpslibusb.c gusb_reset_toggles() + */ if (status == 0) { dbg("%s - starting session ...", __func__); garmin_data_p->state = STATE_ACTIVE; - status = garmin_write_bulk(port, GARMIN_START_SESSION_REQ, - sizeof(GARMIN_START_SESSION_REQ), 0); - if (status >= 0) { - - spin_lock_irqsave(&garmin_data_p->lock, flags); - garmin_data_p->ignorePkts++; - spin_unlock_irqrestore(&garmin_data_p->lock, flags); - - /* not needed, but the win32 driver does it too ... */ + for (i = 0; i < 3; i++) { status = garmin_write_bulk(port, - GARMIN_START_SESSION_REQ2, - sizeof(GARMIN_START_SESSION_REQ2), 0); - if (status >= 0) { - status = 0; - spin_lock_irqsave(&garmin_data_p->lock, flags); - garmin_data_p->ignorePkts++; - spin_unlock_irqrestore(&garmin_data_p->lock, - flags); - } + GARMIN_START_SESSION_REQ, + sizeof(GARMIN_START_SESSION_REQ), 0); + + if (status < 0) + break; } + + if (status > 0) + status = 0; } return status; @@ -962,8 +933,6 @@ static int garmin_init_session(struct usb_serial_port *port) - - static int garmin_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp) { @@ -977,8 +946,6 @@ static int garmin_open(struct tty_struct *tty, garmin_data_p->mode = initial_mode; garmin_data_p->count = 0; garmin_data_p->flags = 0; - atomic_set(&garmin_data_p->req_count, 0); - atomic_set(&garmin_data_p->resp_count, 0); spin_unlock_irqrestore(&garmin_data_p->lock, flags); /* shutdown any bulk reads that might be going on */ @@ -1006,6 +973,7 @@ static void garmin_close(struct usb_serial_port *port) return; mutex_lock(&port->serial->disc_mutex); + if (!port->serial->disconnected) garmin_clear(garmin_data_p); @@ -1013,25 +981,17 @@ static void garmin_close(struct usb_serial_port *port) usb_kill_urb(port->read_urb); usb_kill_urb(port->write_urb); - if (!port->serial->disconnected) { - if (noResponseFromAppLayer(garmin_data_p) || - ((garmin_data_p->flags & CLEAR_HALT_REQUIRED) != 0)) { - process_resetdev_request(port); - garmin_data_p->state = STATE_RESET; - } else { - garmin_data_p->state = STATE_DISCONNECTED; - } - } else { + /* keep reset state so we know that we must start a new session */ + if (garmin_data_p->state != STATE_RESET) garmin_data_p->state = STATE_DISCONNECTED; - } + mutex_unlock(&port->serial->disc_mutex); } + static void garmin_write_bulk_callback(struct urb *urb) { - unsigned long flags; struct usb_serial_port *port = urb->context; - int status = urb->status; if (port) { struct garmin_data *garmin_data_p = @@ -1039,20 +999,13 @@ static void garmin_write_bulk_callback(struct urb *urb) dbg("%s - port %d", __func__, port->number); - if (GARMIN_LAYERID_APPL == getLayerId(urb->transfer_buffer) - && (garmin_data_p->mode == MODE_GARMIN_SERIAL)) { - gsp_send_ack(garmin_data_p, - ((__u8 *)urb->transfer_buffer)[4]); - } + if (GARMIN_LAYERID_APPL == getLayerId(urb->transfer_buffer)) { - if (status) { - dbg("%s - nonzero write bulk status received: %d", - __func__, status); - spin_lock_irqsave(&garmin_data_p->lock, flags); - garmin_data_p->flags |= CLEAR_HALT_REQUIRED; - spin_unlock_irqrestore(&garmin_data_p->lock, flags); + if (garmin_data_p->mode == MODE_GARMIN_SERIAL) { + gsp_send_ack(garmin_data_p, + ((__u8 *)urb->transfer_buffer)[4]); + } } - usb_serial_port_softint(port); } @@ -1108,7 +1061,11 @@ static int garmin_write_bulk(struct usb_serial_port *port, urb->transfer_flags |= URB_ZERO_PACKET; if (GARMIN_LAYERID_APPL == getLayerId(buffer)) { - atomic_inc(&garmin_data_p->req_count); + + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags |= APP_REQ_SEEN; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + if (garmin_data_p->mode == MODE_GARMIN_SERIAL) { pkt_clear(garmin_data_p); garmin_data_p->state = STATE_GSP_WAIT_DATA; @@ -1140,6 +1097,9 @@ static int garmin_write(struct tty_struct *tty, struct usb_serial_port *port, usb_serial_debug_data(debug, &port->dev, __func__, count, buf); + if (garmin_data_p->state == STATE_RESET) + return -EIO; + /* check for our private packets */ if (count >= GARMIN_PKTHDR_LENGTH) { len = PRIVPKTSIZ; @@ -1184,7 +1144,7 @@ static int garmin_write(struct tty_struct *tty, struct usb_serial_port *port, break; case PRIV_PKTID_RESET_REQ: - atomic_inc(&garmin_data_p->req_count); + process_resetdev_request(port); break; case PRIV_PKTID_SET_DEF_MODE: @@ -1200,8 +1160,6 @@ static int garmin_write(struct tty_struct *tty, struct usb_serial_port *port, } } - garmin_data_p->ignorePkts = 0; - if (garmin_data_p->mode == MODE_GARMIN_SERIAL) { return gsp_receive(garmin_data_p, buf, count); } else { /* MODE_NATIVE */ @@ -1224,31 +1182,33 @@ static int garmin_write_room(struct tty_struct *tty) static void garmin_read_process(struct garmin_data *garmin_data_p, unsigned char *data, unsigned data_length) { + unsigned long flags; + if (garmin_data_p->flags & FLAGS_DROP_DATA) { /* abort-transfer cmd is actice */ dbg("%s - pkt dropped", __func__); } else if (garmin_data_p->state != STATE_DISCONNECTED && garmin_data_p->state != STATE_RESET) { - /* remember any appl.layer packets, so we know - if a reset is required or not when closing - the device */ - if (0 == memcmp(data, GARMIN_APP_LAYER_REPLY, - sizeof(GARMIN_APP_LAYER_REPLY))) { - atomic_inc(&garmin_data_p->resp_count); - } - /* if throttling is active or postprecessing is required put the received data in the input queue, otherwise send it directly to the tty port */ if (garmin_data_p->flags & FLAGS_QUEUING) { pkt_add(garmin_data_p, data, data_length); - } else if (garmin_data_p->mode == MODE_GARMIN_SERIAL) { - if (getLayerId(data) == GARMIN_LAYERID_APPL) + } else if (getLayerId(data) == GARMIN_LAYERID_APPL) { + + spin_lock_irqsave(&garmin_data_p->lock, flags); + garmin_data_p->flags |= APP_RESP_SEEN; + spin_unlock_irqrestore(&garmin_data_p->lock, flags); + + if (garmin_data_p->mode == MODE_GARMIN_SERIAL) { pkt_add(garmin_data_p, data, data_length); - } else { - send_to_tty(garmin_data_p->port, data, data_length); + } else { + send_to_tty(garmin_data_p->port, data, + data_length); + } } + /* ignore system layer packets ... */ } } @@ -1363,8 +1323,6 @@ static void garmin_read_int_callback(struct urb *urb) } else { spin_lock_irqsave(&garmin_data_p->lock, flags); garmin_data_p->flags |= FLAGS_BULK_IN_ACTIVE; - /* do not send this packet to the user */ - garmin_data_p->ignorePkts = 1; spin_unlock_irqrestore(&garmin_data_p->lock, flags); } @@ -1391,17 +1349,7 @@ static void garmin_read_int_callback(struct urb *urb) __func__, garmin_data_p->serial_num); } - if (garmin_data_p->ignorePkts) { - /* this reply belongs to a request generated by the driver, - ignore it. */ - dbg("%s - pkt ignored (%d)", - __func__, garmin_data_p->ignorePkts); - spin_lock_irqsave(&garmin_data_p->lock, flags); - garmin_data_p->ignorePkts--; - spin_unlock_irqrestore(&garmin_data_p->lock, flags); - } else { - garmin_read_process(garmin_data_p, data, urb->actual_length); - } + garmin_read_process(garmin_data_p, data, urb->actual_length); port->interrupt_in_urb->dev = port->serial->dev; retval = usb_submit_urb(urb, GFP_ATOMIC); -- cgit v1.2.3 From 5700f4c551f807576eec1d359575681e33057871 Mon Sep 17 00:00:00 2001 From: Paul Mundt <lethal@linux-sh.org> Date: Thu, 30 Apr 2009 17:41:02 +0900 Subject: wusb: hwa-hc: Drop unused pci_suspend/resume hooks. CC drivers/usb/host/hwa-hc.o drivers/usb/host/hwa-hc.c:601: warning: initialization from incompatible pointer type drivers/usb/host/hwa-hc.c:602: warning: initialization from incompatible pointer type The prototype for these changed, so the message itself was dropped. As the only thing these hooks were doing was printing out the message for debugging, there is not much point in keeping them around. So, just kill them off. Cc: Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> Cc: Greg Kroah-Hartman <gregkh@suse.de> Signed-off-by: Paul Mundt <lethal@linux-sh.org> Acked-by: David Vrabel <david.vrabel@csr.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/hwa-hc.c | 21 --------------------- 1 file changed, 21 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index cbf30e515f29..88b03214622b 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -172,25 +172,6 @@ error_cluster_id_get: } -static int hwahc_op_suspend(struct usb_hcd *usb_hcd, pm_message_t msg) -{ - struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); - struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); - dev_err(wusbhc->dev, "%s (%p [%p], 0x%lx) UNIMPLEMENTED\n", __func__, - usb_hcd, hwahc, *(unsigned long *) &msg); - return -ENOSYS; -} - -static int hwahc_op_resume(struct usb_hcd *usb_hcd) -{ - struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); - struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); - - dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, - usb_hcd, hwahc); - return -ENOSYS; -} - /* * No need to abort pipes, as when this is called, all the children * has been disconnected and that has done it [through @@ -598,8 +579,6 @@ static struct hc_driver hwahc_hc_driver = { .flags = HCD_USB2, /* FIXME */ .reset = hwahc_op_reset, .start = hwahc_op_start, - .pci_suspend = hwahc_op_suspend, - .pci_resume = hwahc_op_resume, .stop = hwahc_op_stop, .get_frame_number = hwahc_op_get_frame_number, .urb_enqueue = hwahc_op_urb_enqueue, -- cgit v1.2.3 From e05b8e6e1066e7583dbb6b00407508797b737995 Mon Sep 17 00:00:00 2001 From: Pete Zaitcev <zaitcev@redhat.com> Date: Wed, 29 Apr 2009 16:02:18 -0600 Subject: USB: janitor storage initializers We all know that pointless janitoring is bad, but this code is just offensive. So: - The error code goes directly to probe return, so don't return -1. - Don't return return internal usb-storage codes either. - usb_stor_control_msg takes timeout in milliseconds. - Sanitize messages. Signed-off-by: Pete Zaitcev <zaitcev@redhat.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/storage/initializers.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/storage/initializers.c b/drivers/usb/storage/initializers.c index 2dd9bd4bff56..ec17c96371af 100644 --- a/drivers/usb/storage/initializers.c +++ b/drivers/usb/storage/initializers.c @@ -52,7 +52,7 @@ int usb_stor_euscsi_init(struct us_data *us) us->iobuf[0] = 0x1; result = usb_stor_control_msg(us, us->send_ctrl_pipe, 0x0C, USB_RECIP_INTERFACE | USB_TYPE_VENDOR, - 0x01, 0x0, us->iobuf, 0x1, 5*HZ); + 0x01, 0x0, us->iobuf, 0x1, 5000); US_DEBUGP("-- result is %d\n", result); return 0; @@ -80,14 +80,16 @@ int usb_stor_ucr61s2b_init(struct us_data *us) res = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, bcb, US_BULK_CB_WRAP_LEN, &partial); - if(res) - return res; + if (res) + return -EIO; US_DEBUGP("Getting status packet...\n"); res = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, bcs, US_BULK_CS_WRAP_LEN, &partial); + if (res) + return -EIO; - return (res ? -1 : 0); + return 0; } /* This places the HUAWEI E220 devices in multi-port mode */ @@ -99,6 +101,6 @@ int usb_stor_huawei_e220_init(struct us_data *us) USB_REQ_SET_FEATURE, USB_TYPE_STANDARD | USB_RECIP_DEVICE, 0x01, 0x0, NULL, 0x0, 1000); - US_DEBUGP("usb_control_msg performing result is %d\n", result); - return (result ? 0 : -1); + US_DEBUGP("Huawei mode set result is %d\n", result); + return (result ? 0 : -ENODEV); } -- cgit v1.2.3 From 40d2ff32f102e69d482480265ec60ffb86b028de Mon Sep 17 00:00:00 2001 From: Elina Pasheva <epasheva@sierrawireless.com> Date: Wed, 29 Apr 2009 10:26:46 -0700 Subject: USB: serial: sierra driver write path improvements - Updated Copyright notice with new authors names - Version number set to 1.3.6 - Added a MAX_TRANSFER constant following Greg Kroah-Hartman's recommended setting of PAGE_SIZE-512 for USB transfer buffers and modified accordingly sierra_write() function. Signed-off-by: Elina Pasheva <epasheva@sierrawireless.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/sierra.c | 52 ++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index f75688416b65..27f41f95aefb 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -1,7 +1,10 @@ /* USB Driver for Sierra Wireless - Copyright (C) 2006, 2007, 2008 Kevin Lloyd <klloyd@sierrawireless.com> + Copyright (C) 2006, 2007, 2008 Kevin Lloyd <klloyd@sierrawireless.com>, + + Copyright (C) 2008, 2009 Elina Pasheva, Matthew Safar, Rory Filer + <linux@sierrawireless.com> IMPORTANT DISCLAIMER: This driver is not commercially supported by Sierra Wireless. Use at your own risk. @@ -14,8 +17,8 @@ Whom based his on the Keyspan driver by Hugh Blemings <hugh@blemings.org> */ -#define DRIVER_VERSION "v.1.3.5" -#define DRIVER_AUTHOR "Kevin Lloyd <klloyd@sierrawireless.com>" +#define DRIVER_VERSION "v.1.3.6" +#define DRIVER_AUTHOR "Kevin Lloyd, Elina Pasheva, Matthew Safar, Rory Filer" #define DRIVER_DESC "USB Driver for Sierra Wireless USB modems" #include <linux/kernel.h> @@ -34,6 +37,11 @@ #define N_OUT_URB 64 #define IN_BUFLEN 4096 +#define MAX_TRANSFER (PAGE_SIZE - 512) +/* MAX_TRANSFER is chosen so that the VM is not stressed by + allocations > PAGE_SIZE and the number of packets in a page + is an integer 512 is the largest possible packet on EHCI */ + static int debug; static int nmea; @@ -419,50 +427,58 @@ static int sierra_write(struct tty_struct *tty, struct usb_serial_port *port, unsigned long flags; unsigned char *buffer; struct urb *urb; - int status; + size_t writesize = min((size_t)count, (size_t)MAX_TRANSFER); + int retval = 0; + + /* verify that we actually have some data to write */ + if (count == 0) + return 0; portdata = usb_get_serial_port_data(port); - dev_dbg(&port->dev, "%s: write (%d chars)\n", __func__, count); + dev_dbg(&port->dev, "%s: write (%d bytes)\n", __func__, writesize); spin_lock_irqsave(&portdata->lock, flags); + dev_dbg(&port->dev, "%s - outstanding_urbs: %d\n", __func__, + portdata->outstanding_urbs); if (portdata->outstanding_urbs > N_OUT_URB) { spin_unlock_irqrestore(&portdata->lock, flags); dev_dbg(&port->dev, "%s - write limit hit\n", __func__); return 0; } portdata->outstanding_urbs++; + dev_dbg(&port->dev, "%s - 1, outstanding_urbs: %d\n", __func__, + portdata->outstanding_urbs); spin_unlock_irqrestore(&portdata->lock, flags); - buffer = kmalloc(count, GFP_ATOMIC); + buffer = kmalloc(writesize, GFP_ATOMIC); if (!buffer) { dev_err(&port->dev, "out of memory\n"); - count = -ENOMEM; + retval = -ENOMEM; goto error_no_buffer; } urb = usb_alloc_urb(0, GFP_ATOMIC); if (!urb) { dev_err(&port->dev, "no more free urbs\n"); - count = -ENOMEM; + retval = -ENOMEM; goto error_no_urb; } - memcpy(buffer, buf, count); + memcpy(buffer, buf, writesize); - usb_serial_debug_data(debug, &port->dev, __func__, count, buffer); + usb_serial_debug_data(debug, &port->dev, __func__, writesize, buffer); usb_fill_bulk_urb(urb, serial->dev, usb_sndbulkpipe(serial->dev, port->bulk_out_endpointAddress), - buffer, count, sierra_outdat_callback, port); + buffer, writesize, sierra_outdat_callback, port); /* send it down the pipe */ - status = usb_submit_urb(urb, GFP_ATOMIC); - if (status) { + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) { dev_err(&port->dev, "%s - usb_submit_urb(write bulk) failed " - "with status = %d\n", __func__, status); - count = status; + "with status = %d\n", __func__, retval); goto error; } @@ -470,7 +486,7 @@ static int sierra_write(struct tty_struct *tty, struct usb_serial_port *port, * really free it when it is finished with it */ usb_free_urb(urb); - return count; + return writesize; error: usb_free_urb(urb); error_no_urb: @@ -478,8 +494,10 @@ error_no_urb: error_no_buffer: spin_lock_irqsave(&portdata->lock, flags); --portdata->outstanding_urbs; + dev_dbg(&port->dev, "%s - 2. outstanding_urbs: %d\n", __func__, + portdata->outstanding_urbs); spin_unlock_irqrestore(&portdata->lock, flags); - return count; + return retval; } static void sierra_indat_callback(struct urb *urb) -- cgit v1.2.3 From b0cda8c5f7b652c6c27bcb3891d174534d2f1a91 Mon Sep 17 00:00:00 2001 From: Elina Pasheva <epasheva@sierrawireless.com> Date: Wed, 29 Apr 2009 10:29:21 -0700 Subject: USB: serial: sierra driver read path bug fix This patch fixes a problem in function sierra_indat_callback() which would stop receiving traffic from a modem if a number of URB failures occur. Failed URBs are not resubmitted for the next read and there is only a limited number of URBs allocated for the IN path. After this number of failures, the receive path stops working on a particular interface. Signed-off-by: Elina Pasheva <epasheva@sierrawireless.com> --- drivers/usb/serial/sierra.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 27f41f95aefb..f494c5f659e2 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -509,10 +509,10 @@ static void sierra_indat_callback(struct urb *urb) unsigned char *data = urb->transfer_buffer; int status = urb->status; - dbg("%s: %p", __func__, urb); - endpoint = usb_pipeendpoint(urb->pipe); - port = urb->context; + port = urb->context; + + dev_dbg(&port->dev, "%s: %p\n", __func__, urb); if (status) { dev_dbg(&port->dev, "%s: nonzero status: %d on" @@ -520,22 +520,28 @@ static void sierra_indat_callback(struct urb *urb) } else { if (urb->actual_length) { tty = tty_port_tty_get(&port->port); + tty_buffer_request_room(tty, urb->actual_length); tty_insert_flip_string(tty, data, urb->actual_length); tty_flip_buffer_push(tty); + tty_kref_put(tty); - } else + usb_serial_debug_data(debug, &port->dev, __func__, + urb->actual_length, data); + } else { dev_dbg(&port->dev, "%s: empty read urb" " received\n", __func__); - - /* Resubmit urb so we continue receiving */ - if (port->port.count && status != -ESHUTDOWN && status != -EPERM) { - err = usb_submit_urb(urb, GFP_ATOMIC); - if (err) - dev_err(&port->dev, "resubmit read urb failed." - "(%d)\n", err); } } + + /* Resubmit urb so we continue receiving */ + if (port->port.count && status != -ESHUTDOWN && status != -EPERM) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dev_err(&port->dev, "resubmit read urb failed." + "(%d)\n", err); + } + return; } -- cgit v1.2.3 From 715b1dc01fe44537e8fce9566e4bb48d6821d84b Mon Sep 17 00:00:00 2001 From: Jason Wessel <jason.wessel@windriver.com> Date: Mon, 11 May 2009 15:24:07 -0500 Subject: USB: usb_debug, usb_generic_serial: implement multi urb write The usb_debug driver, when used as the console, will always fail to insert the carriage return and new line sequence as well as randomly drop console output. This is a result of only having the single write_urb and that the tty layer will have a lock that prevents the processing of the back to back urb requests. The solution is to allow more than one urb to be outstanding and have a slightly deeper transmit queue. The idea and some code is borrowed from the ftdi_sio usb driver. The generic usb serial driver was modified so as to allow the classic method of 1 write urb, or a multi write urb scheme with N allowed outstanding urbs where N is controlled by max_in_flight_urbs. When max_in_flight_urbs in a "struct usb_serial_driver" is non zero the multi write urb scheme will be used. The size of 4000 was selected for the usb_debug driver so that the driver lowers possibility of losing the queued console messages during the kernel startup. Signed-off-by: Jason Wessel <jason.wessel@windriver.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/generic.c | 121 ++++++++++++++++++++++++++++++++++++++--- drivers/usb/serial/usb_debug.c | 2 + include/linux/usb/serial.h | 4 ++ 3 files changed, 120 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index be82ea956720..c919686521ff 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -190,6 +190,88 @@ void usb_serial_generic_close(struct usb_serial_port *port) generic_cleanup(port); } +static int usb_serial_multi_urb_write(struct tty_struct *tty, + struct usb_serial_port *port, const unsigned char *buf, int count) +{ + unsigned long flags; + struct urb *urb; + unsigned char *buffer; + int status; + int towrite; + int bwrite = 0; + + dbg("%s - port %d", __func__, port->number); + + if (count == 0) + dbg("%s - write request of 0 bytes", __func__); + + while (count > 0) { + towrite = (count > port->bulk_out_size) ? + port->bulk_out_size : count; + spin_lock_irqsave(&port->lock, flags); + if (port->urbs_in_flight > + port->serial->type->max_in_flight_urbs) { + spin_unlock_irqrestore(&port->lock, flags); + dbg("%s - write limit hit\n", __func__); + return bwrite; + } + port->tx_bytes_flight += towrite; + port->urbs_in_flight++; + spin_unlock_irqrestore(&port->lock, flags); + + buffer = kmalloc(towrite, GFP_ATOMIC); + if (!buffer) { + dev_err(&port->dev, + "%s ran out of kernel memory for urb ...\n", __func__); + goto error_no_buffer; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + dev_err(&port->dev, "%s - no more free urbs\n", + __func__); + goto error_no_urb; + } + + /* Copy data */ + memcpy(buffer, buf + bwrite, towrite); + usb_serial_debug_data(debug, &port->dev, __func__, + towrite, buffer); + /* fill the buffer and send it */ + usb_fill_bulk_urb(urb, port->serial->dev, + usb_sndbulkpipe(port->serial->dev, + port->bulk_out_endpointAddress), + buffer, towrite, + usb_serial_generic_write_bulk_callback, port); + + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + dev_err(&port->dev, + "%s - failed submitting write urb, error %d\n", + __func__, status); + goto error; + } + + /* This urb is the responsibility of the host driver now */ + usb_free_urb(urb); + dbg("%s write: %d", __func__, towrite); + count -= towrite; + bwrite += towrite; + } + return bwrite; + +error: + usb_free_urb(urb); +error_no_urb: + kfree(buffer); +error_no_buffer: + spin_lock_irqsave(&port->lock, flags); + port->urbs_in_flight--; + port->tx_bytes_flight -= towrite; + spin_unlock_irqrestore(&port->lock, flags); + return bwrite; +} + int usb_serial_generic_write(struct tty_struct *tty, struct usb_serial_port *port, const unsigned char *buf, int count) { @@ -207,6 +289,11 @@ int usb_serial_generic_write(struct tty_struct *tty, /* only do something if we have a bulk out endpoint */ if (serial->num_bulk_out) { unsigned long flags; + + if (serial->type->max_in_flight_urbs) + return usb_serial_multi_urb_write(tty, port, + buf, count); + spin_lock_irqsave(&port->lock, flags); if (port->write_urb_busy) { spin_unlock_irqrestore(&port->lock, flags); @@ -257,15 +344,18 @@ int usb_serial_generic_write_room(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; struct usb_serial *serial = port->serial; + unsigned long flags; int room = 0; dbg("%s - port %d", __func__, port->number); - - /* FIXME: Locking */ - if (serial->num_bulk_out) { - if (!(port->write_urb_busy)) + spin_lock_irqsave(&port->lock, flags); + if (serial->type->max_in_flight_urbs) { + if (port->urbs_in_flight < serial->type->max_in_flight_urbs) room = port->bulk_out_size; + } else if (serial->num_bulk_out && !(port->write_urb_busy)) { + room = port->bulk_out_size; } + spin_unlock_irqrestore(&port->lock, flags); dbg("%s - returns %d", __func__, room); return room; @@ -276,11 +366,16 @@ int usb_serial_generic_chars_in_buffer(struct tty_struct *tty) struct usb_serial_port *port = tty->driver_data; struct usb_serial *serial = port->serial; int chars = 0; + unsigned long flags; dbg("%s - port %d", __func__, port->number); - /* FIXME: Locking */ - if (serial->num_bulk_out) { + if (serial->type->max_in_flight_urbs) { + spin_lock_irqsave(&port->lock, flags); + chars = port->tx_bytes_flight; + spin_unlock_irqrestore(&port->lock, flags); + } else if (serial->num_bulk_out) { + /* FIXME: Locking */ if (port->write_urb_busy) chars = port->write_urb->transfer_buffer_length; } @@ -363,12 +458,24 @@ EXPORT_SYMBOL_GPL(usb_serial_generic_read_bulk_callback); void usb_serial_generic_write_bulk_callback(struct urb *urb) { + unsigned long flags; struct usb_serial_port *port = urb->context; int status = urb->status; dbg("%s - port %d", __func__, port->number); - port->write_urb_busy = 0; + if (port->serial->type->max_in_flight_urbs) { + spin_lock_irqsave(&port->lock, flags); + --port->urbs_in_flight; + port->tx_bytes_flight -= urb->transfer_buffer_length; + if (port->urbs_in_flight < 0) + port->urbs_in_flight = 0; + spin_unlock_irqrestore(&port->lock, flags); + } else { + /* Handle the case for single urb mode */ + port->write_urb_busy = 0; + } + if (status) { dbg("%s - nonzero write bulk status received: %d", __func__, status); diff --git a/drivers/usb/serial/usb_debug.c b/drivers/usb/serial/usb_debug.c index 6c9cbb59552a..a9427a8b8672 100644 --- a/drivers/usb/serial/usb_debug.c +++ b/drivers/usb/serial/usb_debug.c @@ -15,6 +15,7 @@ #include <linux/usb.h> #include <linux/usb/serial.h> +#define URB_DEBUG_MAX_IN_FLIGHT_URBS 4000 #define USB_DEBUG_MAX_PACKET_SIZE 8 static struct usb_device_id id_table [] = { @@ -46,6 +47,7 @@ static struct usb_serial_driver debug_device = { .id_table = id_table, .num_ports = 1, .open = usb_debug_open, + .max_in_flight_urbs = URB_DEBUG_MAX_IN_FLIGHT_URBS, }; static int __init debug_init(void) diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index 8cdfed738fe4..e2938fd179e2 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -91,6 +91,9 @@ struct usb_serial_port { int write_urb_busy; __u8 bulk_out_endpointAddress; + int tx_bytes_flight; + int urbs_in_flight; + wait_queue_head_t write_wait; struct work_struct work; char throttled; @@ -207,6 +210,7 @@ struct usb_serial_driver { struct device_driver driver; struct usb_driver *usb_driver; struct usb_dynids dynids; + int max_in_flight_urbs; int (*probe)(struct usb_serial *serial, const struct usb_device_id *id); int (*attach)(struct usb_serial *serial); -- cgit v1.2.3 From 87c1edd217a6742e48028db6664d7763de0449f6 Mon Sep 17 00:00:00 2001 From: Jason Wessel <jason.wessel@windriver.com> Date: Mon, 11 May 2009 15:24:08 -0500 Subject: USB: serial: ftd_sio usb: move status check Alan Stern commented that the private driver counts must be updated regard less of the status return on the urb when the write call back is executed. This patch alters the behavior to update the private driver counts by simply moving the status check to after the driver count update. Signed-off-by: Jason Wessel <jason.wessel@windriver.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/ftdi_sio.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 74cc3c831604..fc527de73465 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1884,11 +1884,6 @@ static void ftdi_write_bulk_callback(struct urb *urb) dbg("%s - port %d", __func__, port->number); - if (status) { - dbg("nonzero write bulk status received: %d", status); - return; - } - priv = usb_get_serial_port_data(port); if (!priv) { dbg("%s - bad port private data pointer - exiting", __func__); @@ -1906,6 +1901,11 @@ static void ftdi_write_bulk_callback(struct urb *urb) priv->tx_outstanding_bytes -= countback; spin_unlock_irqrestore(&priv->tx_lock, flags); + if (status) { + dbg("nonzero write bulk status received: %d", status); + return; + } + usb_serial_port_softint(port); } /* ftdi_write_bulk_callback */ -- cgit v1.2.3 From 98fcb5f78165b8a3d93870ad7afd4d9ebbb8b43a Mon Sep 17 00:00:00 2001 From: Jason Wessel <jason.wessel@windriver.com> Date: Mon, 11 May 2009 15:24:09 -0500 Subject: USB: serial: usb_debug,usb_generic_serial: implement sysrq and serial break The usb_debug driver was modified to implement serial break handling by using a "magic" data packet comprised of the sequence: 0x00 0xff 0x01 0xfe 0x00 0xfe 0x01 0xff When the tty layer requests a serial break the usb_debug driver sends the magic packet. On the receiving side the magic packet is thrown away or a sysrq is activated depending on what kernel .config options have been set. The generic serial driver was modified as well as the usb serial headers to generically implement sysrq processing in the same way the non usb uart based drivers implement the sysrq handling. This will allow other usb serial devices to implement sysrq handling as desired. The new usb serial functions are named similarly and implemented similarly to the uart functions as follows: usb_serial_handle_break <-> uart_handle_break usb_serial_handle_sysrq_char <-> uart_handle_sysrq_char Signed-off-by: Jason Wessel <jason.wessel@windriver.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/generic.c | 56 +++++++++++++++++++++++++++++++++--------- drivers/usb/serial/usb_debug.c | 39 +++++++++++++++++++++++++++++ include/linux/usb/serial.h | 8 ++++++ 3 files changed, 91 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index c919686521ff..9fccc26235a2 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -339,6 +339,7 @@ int usb_serial_generic_write(struct tty_struct *tty, /* no bulk out, so return 0 bytes written */ return 0; } +EXPORT_SYMBOL_GPL(usb_serial_generic_write); int usb_serial_generic_write_room(struct tty_struct *tty) { @@ -351,7 +352,9 @@ int usb_serial_generic_write_room(struct tty_struct *tty) spin_lock_irqsave(&port->lock, flags); if (serial->type->max_in_flight_urbs) { if (port->urbs_in_flight < serial->type->max_in_flight_urbs) - room = port->bulk_out_size; + room = port->bulk_out_size * + (serial->type->max_in_flight_urbs - + port->urbs_in_flight); } else if (serial->num_bulk_out && !(port->write_urb_busy)) { room = port->bulk_out_size; } @@ -385,7 +388,8 @@ int usb_serial_generic_chars_in_buffer(struct tty_struct *tty) } -static void resubmit_read_urb(struct usb_serial_port *port, gfp_t mem_flags) +void usb_serial_generic_resubmit_read_urb(struct usb_serial_port *port, + gfp_t mem_flags) { struct urb *urb = port->read_urb; struct usb_serial *serial = port->serial; @@ -406,25 +410,28 @@ static void resubmit_read_urb(struct usb_serial_port *port, gfp_t mem_flags) "%s - failed resubmitting read urb, error %d\n", __func__, result); } +EXPORT_SYMBOL_GPL(usb_serial_generic_resubmit_read_urb); /* Push data to tty layer and resubmit the bulk read URB */ static void flush_and_resubmit_read_urb(struct usb_serial_port *port) { struct urb *urb = port->read_urb; struct tty_struct *tty = tty_port_tty_get(&port->port); - int room; + char *ch = (char *)urb->transfer_buffer; + int i; + + if (!tty) + goto done; /* Push data to tty */ - if (tty && urb->actual_length) { - room = tty_buffer_request_room(tty, urb->actual_length); - if (room) { - tty_insert_flip_string(tty, urb->transfer_buffer, room); - tty_flip_buffer_push(tty); - } + for (i = 0; i < urb->actual_length; i++, ch++) { + if (!usb_serial_handle_sysrq_char(port, *ch)) + tty_insert_flip_char(tty, *ch, TTY_NORMAL); } + tty_flip_buffer_push(tty); tty_kref_put(tty); - - resubmit_read_urb(port, GFP_ATOMIC); +done: + usb_serial_generic_resubmit_read_urb(port, GFP_ATOMIC); } void usb_serial_generic_read_bulk_callback(struct urb *urb) @@ -515,10 +522,35 @@ void usb_serial_generic_unthrottle(struct tty_struct *tty) if (was_throttled) { /* Resume reading from device */ - resubmit_read_urb(port, GFP_KERNEL); + usb_serial_generic_resubmit_read_urb(port, GFP_KERNEL); } } +int usb_serial_handle_sysrq_char(struct usb_serial_port *port, unsigned int ch) +{ + if (port->sysrq) { + if (ch && time_before(jiffies, port->sysrq)) { + handle_sysrq(ch, tty_port_tty_get(&port->port)); + port->sysrq = 0; + return 1; + } + port->sysrq = 0; + } + return 0; +} +EXPORT_SYMBOL_GPL(usb_serial_handle_sysrq_char); + +int usb_serial_handle_break(struct usb_serial_port *port) +{ + if (!port->sysrq) { + port->sysrq = jiffies + HZ*5; + return 1; + } + port->sysrq = 0; + return 0; +} +EXPORT_SYMBOL_GPL(usb_serial_handle_break); + void usb_serial_generic_shutdown(struct usb_serial *serial) { int i; diff --git a/drivers/usb/serial/usb_debug.c b/drivers/usb/serial/usb_debug.c index a9427a8b8672..614800972dc3 100644 --- a/drivers/usb/serial/usb_debug.c +++ b/drivers/usb/serial/usb_debug.c @@ -17,6 +17,17 @@ #define URB_DEBUG_MAX_IN_FLIGHT_URBS 4000 #define USB_DEBUG_MAX_PACKET_SIZE 8 +#define USB_DEBUG_BRK_SIZE 8 +static char USB_DEBUG_BRK[USB_DEBUG_BRK_SIZE] = { + 0x00, + 0xff, + 0x01, + 0xfe, + 0x00, + 0xfe, + 0x01, + 0xff, +}; static struct usb_device_id id_table [] = { { USB_DEVICE(0x0525, 0x127a) }, @@ -39,6 +50,32 @@ static int usb_debug_open(struct tty_struct *tty, struct usb_serial_port *port, return usb_serial_generic_open(tty, port, filp); } +/* This HW really does not support a serial break, so one will be + * emulated when ever the break state is set to true. + */ +static void usb_debug_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + if (!break_state) + return; + usb_serial_generic_write(tty, port, USB_DEBUG_BRK, USB_DEBUG_BRK_SIZE); +} + +static void usb_debug_read_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + + if (urb->actual_length == USB_DEBUG_BRK_SIZE && + memcmp(urb->transfer_buffer, USB_DEBUG_BRK, + USB_DEBUG_BRK_SIZE) == 0) { + usb_serial_handle_break(port); + usb_serial_generic_resubmit_read_urb(port, GFP_ATOMIC); + return; + } + + usb_serial_generic_read_bulk_callback(urb); +} + static struct usb_serial_driver debug_device = { .driver = { .owner = THIS_MODULE, @@ -48,6 +85,8 @@ static struct usb_serial_driver debug_device = { .num_ports = 1, .open = usb_debug_open, .max_in_flight_urbs = URB_DEBUG_MAX_IN_FLIGHT_URBS, + .break_ctl = usb_debug_break_ctl, + .read_bulk_callback = usb_debug_read_bulk_callback, }; static int __init debug_init(void) diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index e2938fd179e2..e29ebcf3287b 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -15,6 +15,7 @@ #include <linux/kref.h> #include <linux/mutex.h> +#include <linux/sysrq.h> #define SERIAL_TTY_MAJOR 188 /* Nice legal number now */ #define SERIAL_TTY_MINORS 254 /* loads of devices :) */ @@ -99,6 +100,7 @@ struct usb_serial_port { char throttled; char throttle_req; char console; + unsigned long sysrq; /* sysrq timeout */ struct device dev; }; #define to_usb_serial_port(d) container_of(d, struct usb_serial_port, dev) @@ -301,6 +303,12 @@ extern void usb_serial_generic_unthrottle(struct tty_struct *tty); extern void usb_serial_generic_shutdown(struct usb_serial *serial); extern int usb_serial_generic_register(int debug); extern void usb_serial_generic_deregister(void); +extern void usb_serial_generic_resubmit_read_urb(struct usb_serial_port *port, + gfp_t mem_flags); +extern int usb_serial_handle_sysrq_char(struct usb_serial_port *port, + unsigned int ch); +extern int usb_serial_handle_break(struct usb_serial_port *port); + extern int usb_serial_bus_register(struct usb_serial_driver *device); extern void usb_serial_bus_deregister(struct usb_serial_driver *device); -- cgit v1.2.3 From 72fda3ca6fc14662bb385d1e39e9e00af15b200d Mon Sep 17 00:00:00 2001 From: Jason Wessel <jason.wessel@windriver.com> Date: Mon, 11 May 2009 15:24:10 -0500 Subject: USB: serial: ftd_sio: implement sysrq handling on break Change driver to make use of the new functions in include/linux/usb/serial.h so as to allow the driver to handle the sysrq Signed-off-by: Jason Wessel <jason.wessel@windriver.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/ftdi_sio.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index fc527de73465..fc423583eede 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -2120,6 +2120,7 @@ static void ftdi_process_read(struct work_struct *work) if (data[packet_offset+1] & FTDI_RS_BI) { error_flag = TTY_BREAK; dbg("BREAK received"); + usb_serial_handle_break(port); } if (data[packet_offset+1] & FTDI_RS_PE) { error_flag = TTY_PARITY; @@ -2134,8 +2135,11 @@ static void ftdi_process_read(struct work_struct *work) /* Note that the error flag is duplicated for every character received since we don't know which character it applied to */ - tty_insert_flip_char(tty, - data[packet_offset + i], error_flag); + if (!usb_serial_handle_sysrq_char(port, + data[packet_offset + i])) + tty_insert_flip_char(tty, + data[packet_offset + i], + error_flag); } need_flip = 1; } -- cgit v1.2.3 From 238ebd138c9704e1162030c1e600bc06142cb2c8 Mon Sep 17 00:00:00 2001 From: Elina Pasheva <epasheva@sierrawireless.com> Date: Tue, 12 May 2009 13:12:24 -0700 Subject: USB: serial: sierra driver zero length packet fix - Fixed a problem with transferring packets with size a multiple of Bulk Xfer size in function sierra_write(). Added transfer flag URB_ZERO_PACKET before submitting the urb to trigger Zero-length data transfer when packet size is a multiple of Bulk Xfer. Signed-off-by: Elina Pasheva <epasheva@sierrawireless.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/sierra.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index f494c5f659e2..9d11fe47633b 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -17,7 +17,7 @@ Whom based his on the Keyspan driver by Hugh Blemings <hugh@blemings.org> */ -#define DRIVER_VERSION "v.1.3.6" +#define DRIVER_VERSION "v.1.3.7" #define DRIVER_AUTHOR "Kevin Lloyd, Elina Pasheva, Matthew Safar, Rory Filer" #define DRIVER_DESC "USB Driver for Sierra Wireless USB modems" @@ -474,6 +474,9 @@ static int sierra_write(struct tty_struct *tty, struct usb_serial_port *port, port->bulk_out_endpointAddress), buffer, writesize, sierra_outdat_callback, port); + /* Handle the need to send a zero length packet */ + urb->transfer_flags |= URB_ZERO_PACKET; + /* send it down the pipe */ retval = usb_submit_urb(urb, GFP_ATOMIC); if (retval) { -- cgit v1.2.3 From c76a23da8e9a39222c4a7c29b0c5348cd8902a2b Mon Sep 17 00:00:00 2001 From: Elina Pasheva <epasheva@sierrawireless.com> Date: Tue, 12 May 2009 13:12:37 -0700 Subject: USB: serial: sierra driver interrupt urb resubmit fix - Fixed a problem when re-submitting urb from interrupt callback in function sierra_instat_callback(). This suppresses also issuing of error messages in /var/log/kern.log - Removed redundant debug message at the beginning of sierra_instat_callback() function - Changed a debug message to be an error message Signed-off-by: Elina Pasheva <epasheva@sierrawireless.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/sierra.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 9d11fe47633b..bcfe0ac1e221 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -556,7 +556,6 @@ static void sierra_instat_callback(struct urb *urb) struct sierra_port_private *portdata = usb_get_serial_port_data(port); struct usb_serial *serial = port->serial; - dev_dbg(&port->dev, "%s\n", __func__); dev_dbg(&port->dev, "%s: urb %p port %p has data %p\n", __func__, urb, port, portdata); @@ -600,12 +599,12 @@ static void sierra_instat_callback(struct urb *urb) dev_dbg(&port->dev, "%s: error %d\n", __func__, status); /* Resubmit urb so we continue receiving IRQ data */ - if (status != -ESHUTDOWN) { + if (port->port.count && status != -ESHUTDOWN && status != -ENOENT) { urb->dev = serial->dev; err = usb_submit_urb(urb, GFP_ATOMIC); if (err) - dev_dbg(&port->dev, "%s: resubmit intr urb " - "failed. (%d)\n", __func__, err); + dev_err(&port->dev, "%s: resubmit intr urb " + "failed. (%d)\n", __func__, err); } } -- cgit v1.2.3 From 9636b683769a6c3acfe121314ee9d7e19157f109 Mon Sep 17 00:00:00 2001 From: Elina Pasheva <epasheva@sierrawireless.com> Date: Tue, 12 May 2009 13:12:54 -0700 Subject: USB: serial: sierra driver sierra_calc_num_ports() fix - Removed potential kernel oops from sierra_calc_num_ports() function. Calling this function twice would likely have caused an oops because the function releases allocated memory after the first call. - Modified sierra_probe() function to reflect the changes in sierra_calc_num_ports(). Signed-off-by: Elina Pasheva <epasheva@sierrawireless.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/sierra.c | 49 +++++++++++++-------------------------------- 1 file changed, 14 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index bcfe0ac1e221..f047ab5fa97b 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -83,18 +83,22 @@ static int sierra_vsc_set_nmea(struct usb_device *udev, __u16 enable) static int sierra_calc_num_ports(struct usb_serial *serial) { - int result; - int *num_ports = usb_get_serial_data(serial); - dev_dbg(&serial->dev->dev, "%s\n", __func__); + int num_ports = 0; + u8 ifnum, numendpoints; - result = *num_ports; + dev_dbg(&serial->dev->dev, "%s\n", __func__); - if (result) { - kfree(num_ports); - usb_set_serial_data(serial, NULL); - } + ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber; + numendpoints = serial->interface->cur_altsetting->desc.bNumEndpoints; - return result; + /* Dummy interface present on some SKUs should be ignored */ + if (ifnum == 0x99) + num_ports = 0; + else if (numendpoints <= 3) + num_ports = 1; + else + num_ports = (numendpoints-1)/2; + return num_ports; } static int is_blacklisted(const u8 ifnum, @@ -140,23 +144,12 @@ static int sierra_probe(struct usb_serial *serial, { int result = 0; struct usb_device *udev; - int *num_ports; u8 ifnum; - u8 numendpoints; - - dev_dbg(&serial->dev->dev, "%s\n", __func__); - num_ports = kmalloc(sizeof(*num_ports), GFP_KERNEL); - if (!num_ports) - return -ENOMEM; - - ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber; - numendpoints = serial->interface->cur_altsetting->desc.bNumEndpoints; udev = serial->dev; + dev_dbg(&udev->dev, "%s\n", __func__); - /* Figure out the interface number from the serial structure */ ifnum = sierra_calc_interface(serial); - /* * If this interface supports more than 1 alternate * select the 2nd one @@ -168,20 +161,6 @@ static int sierra_probe(struct usb_serial *serial, usb_set_interface(udev, ifnum, 1); } - /* Dummy interface present on some SKUs should be ignored */ - if (ifnum == 0x99) - *num_ports = 0; - else if (numendpoints <= 3) - *num_ports = 1; - else - *num_ports = (numendpoints-1)/2; - - /* - * save off our num_ports info so that we can use it in the - * calc_num_ports callback - */ - usb_set_serial_data(serial, (void *)num_ports); - /* ifnum could have changed - by calling usb_set_interface */ ifnum = sierra_calc_interface(serial); -- cgit v1.2.3 From 5512966643adb17483efc5f61481a38fc33088bb Mon Sep 17 00:00:00 2001 From: Kay Sievers <kay.sievers@vrfy.org> Date: Mon, 4 May 2009 19:48:32 +0200 Subject: usb: convert endpoint devices to bus-less childs of the usb interface The endpoint devices look like simple attribute groups now, and no longer like devices with a specific subsystem. They will also no longer emit uevents. It also removes the device node requests for endpoint devices, which are not implemented for now. Signed-off-by: Kay Sievers <kay.sievers@vrfy.org> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/driver.c | 25 +++---- drivers/usb/core/endpoint.c | 160 +++----------------------------------------- drivers/usb/core/usb.c | 3 +- drivers/usb/core/usb.h | 11 +++ 4 files changed, 29 insertions(+), 170 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index c115eed0fdc3..77de8d65862c 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -154,16 +154,11 @@ static const struct usb_device_id *usb_match_dynamic_id(struct usb_interface *in static int usb_probe_device(struct device *dev) { struct usb_device_driver *udriver = to_usb_device_driver(dev->driver); - struct usb_device *udev; + struct usb_device *udev = to_usb_device(dev); int error = -ENODEV; dev_dbg(dev, "%s\n", __func__); - if (!is_usb_device(dev)) /* Sanity check */ - return error; - - udev = to_usb_device(dev); - /* TODO: Add real matching code */ /* The device should always appear to be in use @@ -203,18 +198,13 @@ static void usb_cancel_queued_reset(struct usb_interface *iface) static int usb_probe_interface(struct device *dev) { struct usb_driver *driver = to_usb_driver(dev->driver); - struct usb_interface *intf; - struct usb_device *udev; + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); const struct usb_device_id *id; int error = -ENODEV; dev_dbg(dev, "%s\n", __func__); - if (is_usb_device(dev)) /* Sanity check */ - return error; - - intf = to_usb_interface(dev); - udev = interface_to_usbdev(intf); intf->needs_binding = 0; if (udev->authorized == 0) { @@ -593,7 +583,7 @@ static int usb_device_match(struct device *dev, struct device_driver *drv) /* TODO: Add real matching code */ return 1; - } else { + } else if (is_usb_interface(dev)) { struct usb_interface *intf; struct usb_driver *usb_drv; const struct usb_device_id *id; @@ -625,11 +615,14 @@ static int usb_uevent(struct device *dev, struct kobj_uevent_env *env) /* driver is often null here; dev_dbg() would oops */ pr_debug("usb %s: uevent\n", dev_name(dev)); - if (is_usb_device(dev)) + if (is_usb_device(dev)) { usb_dev = to_usb_device(dev); - else { + } else if (is_usb_interface(dev)) { struct usb_interface *intf = to_usb_interface(dev); + usb_dev = interface_to_usbdev(intf); + } else { + return 0; } if (usb_dev->devnum < 0) { diff --git a/drivers/usb/core/endpoint.c b/drivers/usb/core/endpoint.c index 40dee2ac0133..bc39fc40bbde 100644 --- a/drivers/usb/core/endpoint.c +++ b/drivers/usb/core/endpoint.c @@ -15,19 +15,18 @@ #include <linux/usb.h> #include "usb.h" -#define MAX_ENDPOINT_MINORS (64*128*32) -static int usb_endpoint_major; -static DEFINE_IDR(endpoint_idr); - struct ep_device { struct usb_endpoint_descriptor *desc; struct usb_device *udev; struct device dev; - int minor; }; #define to_ep_device(_dev) \ container_of(_dev, struct ep_device, dev) +struct device_type usb_ep_device_type = { + .name = "usb_endpoint", +}; + struct ep_attribute { struct attribute attr; ssize_t (*show)(struct usb_device *, @@ -160,118 +159,10 @@ static struct attribute_group *ep_dev_groups[] = { NULL }; -static int usb_endpoint_major_init(void) -{ - dev_t dev; - int error; - - error = alloc_chrdev_region(&dev, 0, MAX_ENDPOINT_MINORS, - "usb_endpoint"); - if (error) { - printk(KERN_ERR "Unable to get a dynamic major for " - "usb endpoints.\n"); - return error; - } - usb_endpoint_major = MAJOR(dev); - - return error; -} - -static void usb_endpoint_major_cleanup(void) -{ - unregister_chrdev_region(MKDEV(usb_endpoint_major, 0), - MAX_ENDPOINT_MINORS); -} - -static int endpoint_get_minor(struct ep_device *ep_dev) -{ - static DEFINE_MUTEX(minor_lock); - int retval = -ENOMEM; - int id; - - mutex_lock(&minor_lock); - if (idr_pre_get(&endpoint_idr, GFP_KERNEL) == 0) - goto exit; - - retval = idr_get_new(&endpoint_idr, ep_dev, &id); - if (retval < 0) { - if (retval == -EAGAIN) - retval = -ENOMEM; - goto exit; - } - ep_dev->minor = id & MAX_ID_MASK; -exit: - mutex_unlock(&minor_lock); - return retval; -} - -static void endpoint_free_minor(struct ep_device *ep_dev) -{ - idr_remove(&endpoint_idr, ep_dev->minor); -} - -static struct endpoint_class { - struct kref kref; - struct class *class; -} *ep_class; - -static int init_endpoint_class(void) -{ - int result = 0; - - if (ep_class != NULL) { - kref_get(&ep_class->kref); - goto exit; - } - - ep_class = kmalloc(sizeof(*ep_class), GFP_KERNEL); - if (!ep_class) { - result = -ENOMEM; - goto exit; - } - - kref_init(&ep_class->kref); - ep_class->class = class_create(THIS_MODULE, "usb_endpoint"); - if (IS_ERR(ep_class->class)) { - result = PTR_ERR(ep_class->class); - goto class_create_error; - } - - result = usb_endpoint_major_init(); - if (result) - goto endpoint_major_error; - - goto exit; - -endpoint_major_error: - class_destroy(ep_class->class); -class_create_error: - kfree(ep_class); - ep_class = NULL; -exit: - return result; -} - -static void release_endpoint_class(struct kref *kref) -{ - /* Ok, we cheat as we know we only have one ep_class */ - class_destroy(ep_class->class); - kfree(ep_class); - ep_class = NULL; - usb_endpoint_major_cleanup(); -} - -static void destroy_endpoint_class(void) -{ - if (ep_class) - kref_put(&ep_class->kref, release_endpoint_class); -} - static void ep_device_release(struct device *dev) { struct ep_device *ep_dev = to_ep_device(dev); - endpoint_free_minor(ep_dev); kfree(ep_dev); } @@ -279,62 +170,32 @@ int usb_create_ep_devs(struct device *parent, struct usb_host_endpoint *endpoint, struct usb_device *udev) { - char name[8]; struct ep_device *ep_dev; int retval; - retval = init_endpoint_class(); - if (retval) - goto exit; - ep_dev = kzalloc(sizeof(*ep_dev), GFP_KERNEL); if (!ep_dev) { retval = -ENOMEM; - goto error_alloc; - } - - retval = endpoint_get_minor(ep_dev); - if (retval) { - dev_err(parent, "can not allocate minor number for %s\n", - dev_name(&ep_dev->dev)); - goto error_register; + goto exit; } ep_dev->desc = &endpoint->desc; ep_dev->udev = udev; ep_dev->dev.groups = ep_dev_groups; - ep_dev->dev.devt = MKDEV(usb_endpoint_major, ep_dev->minor); - ep_dev->dev.class = ep_class->class; + ep_dev->dev.type = &usb_ep_device_type; ep_dev->dev.parent = parent; ep_dev->dev.release = ep_device_release; - dev_set_name(&ep_dev->dev, "usbdev%d.%d_ep%02x", - udev->bus->busnum, udev->devnum, - endpoint->desc.bEndpointAddress); + dev_set_name(&ep_dev->dev, "ep_%02x", endpoint->desc.bEndpointAddress); retval = device_register(&ep_dev->dev); if (retval) - goto error_chrdev; + goto error_register; - /* create the symlink to the old-style "ep_XX" directory */ - sprintf(name, "ep_%02x", endpoint->desc.bEndpointAddress); - retval = sysfs_create_link(&parent->kobj, &ep_dev->dev.kobj, name); - if (retval) - goto error_link; endpoint->ep_dev = ep_dev; return retval; -error_link: - device_unregister(&ep_dev->dev); - destroy_endpoint_class(); - return retval; - -error_chrdev: - endpoint_free_minor(ep_dev); - error_register: kfree(ep_dev); -error_alloc: - destroy_endpoint_class(); exit: return retval; } @@ -344,12 +205,7 @@ void usb_remove_ep_devs(struct usb_host_endpoint *endpoint) struct ep_device *ep_dev = endpoint->ep_dev; if (ep_dev) { - char name[8]; - - sprintf(name, "ep_%02x", endpoint->desc.bEndpointAddress); - sysfs_remove_link(&ep_dev->dev.parent->kobj, name); device_unregister(&ep_dev->dev); endpoint->ep_dev = NULL; - destroy_endpoint_class(); } } diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index eb810bbe7bbc..c32811ae128f 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -140,8 +140,7 @@ static int __find_interface(struct device *dev, void *data) struct find_interface_arg *arg = data; struct usb_interface *intf; - /* can't look at usb devices, only interfaces */ - if (is_usb_device(dev)) + if (!is_usb_interface(dev)) return 0; intf = to_usb_interface(dev); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index dabf9255a10e..e2a8cfaade1d 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -104,6 +104,7 @@ extern struct workqueue_struct *ksuspend_usb_wq; extern struct bus_type usb_bus_type; extern struct device_type usb_device_type; extern struct device_type usb_if_device_type; +extern struct device_type usb_ep_device_type; extern struct usb_device_driver usb_generic_driver; static inline int is_usb_device(const struct device *dev) @@ -111,6 +112,16 @@ static inline int is_usb_device(const struct device *dev) return dev->type == &usb_device_type; } +static inline int is_usb_interface(const struct device *dev) +{ + return dev->type == &usb_if_device_type; +} + +static inline int is_usb_endpoint(const struct device *dev) +{ + return dev->type == &usb_ep_device_type; +} + /* Do the same for device drivers and interface drivers. */ static inline int is_usb_device_driver(struct device_driver *drv) -- cgit v1.2.3 From 1e0abb7e1844a7cb499321a94d5d04347ef86d68 Mon Sep 17 00:00:00 2001 From: Daniel Mack <daniel@caiaq.de> Date: Tue, 12 May 2009 13:50:34 -0700 Subject: USB: imx_udc: fix leak in imx_ep_alloc_request() cppcheck found another leak in drivers/usb/gadget/imx_udc.c Cc: Mike Lee <eemike@gmail.com> Cc: Darius Augulis <augulis.darius@gmail.com> Signed-off-by: Daniel Mack <daniel@caiaq.de> Cc: David Brownell <david-b@pacbell.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/imx_udc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/imx_udc.c b/drivers/usb/gadget/imx_udc.c index 168658b4b4e2..239bf8ed9f4a 100644 --- a/drivers/usb/gadget/imx_udc.c +++ b/drivers/usb/gadget/imx_udc.c @@ -734,9 +734,12 @@ static struct usb_request *imx_ep_alloc_request { struct imx_request *req; + if (!usb_ep) + return NULL; + req = kzalloc(sizeof *req, gfp_flags); - if (!req || !usb_ep) - return 0; + if (!req) + return NULL; INIT_LIST_HEAD(&req->queue); req->in_use = 0; -- cgit v1.2.3 From 0b10395ab86c11bef10e882a4323367e6735c9b2 Mon Sep 17 00:00:00 2001 From: Andrew Morton <akpm@linux-foundation.org> Date: Tue, 12 May 2009 15:27:59 -0700 Subject: + drivers-usb-serial-sierrac-fix-printk-warning.patch added to -mm tree drivers/usb/serial/sierra.c: In function 'sierra_write': drivers/usb/serial/sierra.c:375: warning: format '%d' expects type 'int', but argument 5 has type 'size_t' Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Cc: Elina Pasheva <epasheva@sierrawireless.com> Cc: Rory Filer <rfiler@SierraWireless.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/sierra.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index f047ab5fa97b..a88cde950161 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -415,7 +415,7 @@ static int sierra_write(struct tty_struct *tty, struct usb_serial_port *port, portdata = usb_get_serial_port_data(port); - dev_dbg(&port->dev, "%s: write (%d bytes)\n", __func__, writesize); + dev_dbg(&port->dev, "%s: write (%zd bytes)\n", __func__, writesize); spin_lock_irqsave(&portdata->lock, flags); dev_dbg(&port->dev, "%s - outstanding_urbs: %d\n", __func__, -- cgit v1.2.3 From a2bfb4a346d2c2e25f84b35c6044ff53296be1ee Mon Sep 17 00:00:00 2001 From: Oliver Neukum <oliver@neukum.org> Date: Sat, 16 May 2009 21:13:19 +0200 Subject: USB: support for cdc-acm of single interface devices This implement support in cdc-acm for acm devices another popular OS can handle - adds support for autodetection of devices that use one interface - autodetection of endpoints - add a quirk for surpressing a setting that OS doesn't use - autoassume that quirk for single interface devices Signed-off-by: Oliver Neukum <oliver@neukum.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/class/cdc-acm.c | 68 +++++++++++++++++++++++++++++++++++++-------- drivers/usb/class/cdc-acm.h | 2 ++ 2 files changed, 58 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index ddeb69192537..778e023d1467 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -937,9 +937,9 @@ static int acm_probe(struct usb_interface *intf, int buflen = intf->altsetting->extralen; struct usb_interface *control_interface; struct usb_interface *data_interface; - struct usb_endpoint_descriptor *epctrl; - struct usb_endpoint_descriptor *epread; - struct usb_endpoint_descriptor *epwrite; + struct usb_endpoint_descriptor *epctrl = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; struct usb_device *usb_dev = interface_to_usbdev(intf); struct acm *acm; int minor; @@ -952,6 +952,7 @@ static int acm_probe(struct usb_interface *intf, unsigned long quirks; int num_rx_buf; int i; + int combined_interfaces = 0; /* normal quirks */ quirks = (unsigned long)id->driver_info; @@ -1033,9 +1034,15 @@ next_desc: data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num)); control_interface = intf; } else { - dev_dbg(&intf->dev, - "No union descriptor, giving up\n"); - return -ENODEV; + if (intf->cur_altsetting->desc.bNumEndpoints != 3) { + dev_dbg(&intf->dev,"No union descriptor, giving up\n"); + return -ENODEV; + } else { + dev_warn(&intf->dev,"No union descriptor, testing for castrated device\n"); + combined_interfaces = 1; + control_interface = data_interface = intf; + goto look_for_collapsed_interface; + } } } else { control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); @@ -1049,6 +1056,36 @@ next_desc: if (data_interface_num != call_interface_num) dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n"); + if (control_interface == data_interface) { + /* some broken devices designed for windows work this way */ + dev_warn(&intf->dev,"Control and data interfaces are not separated!\n"); + combined_interfaces = 1; + /* a popular other OS doesn't use it */ + quirks |= NO_CAP_LINE; + if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) { + dev_err(&intf->dev, "This needs exactly 3 endpoints\n"); + return -EINVAL; + } +look_for_collapsed_interface: + for (i = 0; i < 3; i++) { + struct usb_endpoint_descriptor *ep; + ep = &data_interface->cur_altsetting->endpoint[i].desc; + + if (usb_endpoint_is_int_in(ep)) + epctrl = ep; + else if (usb_endpoint_is_bulk_out(ep)) + epwrite = ep; + else if (usb_endpoint_is_bulk_in(ep)) + epread = ep; + else + return -EINVAL; + } + if (!epctrl || !epread || !epwrite) + return -ENODEV; + else + goto made_compressed_probe; + } + skip_normal_probe: /*workaround for switched interfaces */ @@ -1068,10 +1105,11 @@ skip_normal_probe: } /* Accept probe requests only for the control interface */ - if (intf != control_interface) + if (!combined_interfaces && intf != control_interface) return -ENODEV; - if (usb_interface_claimed(data_interface)) { /* valid in this context */ + if (!combined_interfaces && usb_interface_claimed(data_interface)) { + /* valid in this context */ dev_dbg(&intf->dev, "The data interface isn't available\n"); return -EBUSY; } @@ -1095,6 +1133,7 @@ skip_normal_probe: epread = epwrite; epwrite = t; } +made_compressed_probe: dbg("interfaces are valid"); for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++); @@ -1112,12 +1151,15 @@ skip_normal_probe: ctrlsize = le16_to_cpu(epctrl->wMaxPacketSize); readsize = le16_to_cpu(epread->wMaxPacketSize) * (quirks == SINGLE_RX_URB ? 1 : 2); + acm->combined_interfaces = combined_interfaces; acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize) * 20; acm->control = control_interface; acm->data = data_interface; acm->minor = minor; acm->dev = usb_dev; acm->ctrl_caps = ac_management_function; + if (quirks & NO_CAP_LINE) + acm->ctrl_caps &= ~USB_CDC_CAP_LINE; acm->ctrlsize = ctrlsize; acm->readsize = readsize; acm->rx_buflimit = num_rx_buf; @@ -1223,9 +1265,10 @@ skip_normal_probe: skip_countries: usb_fill_int_urb(acm->ctrlurb, usb_dev, - usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), - acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, - epctrl->bInterval); + usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, + /* works around buggy devices */ + epctrl->bInterval ? epctrl->bInterval : 0xff); acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; acm->ctrlurb->transfer_dma = acm->ctrl_dma; @@ -1312,7 +1355,8 @@ static void acm_disconnect(struct usb_interface *intf) acm->ctrl_dma); acm_read_buffers_free(acm); - usb_driver_release_interface(&acm_driver, intf == acm->control ? + if (!acm->combined_interfaces) + usb_driver_release_interface(&acm_driver, intf == acm->control ? acm->data : acm->control); if (acm->port.count == 0) { diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 4c3856420add..1602324808ba 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -125,6 +125,7 @@ struct acm { unsigned char clocal; /* termios CLOCAL */ unsigned int ctrl_caps; /* control capabilities from the class specific header */ unsigned int susp_count; /* number of suspended interfaces */ + int combined_interfaces:1; /* control and data collapsed */ struct acm_wb *delayed_wb; /* write queued for a device about to be woken */ }; @@ -133,3 +134,4 @@ struct acm { /* constants describing various quirks and errors */ #define NO_UNION_NORMAL 1 #define SINGLE_RX_URB 2 +#define NO_CAP_LINE 4 -- cgit v1.2.3 From 9a4b5e36ce58febdd2039dd4d129ee7edf4f5536 Mon Sep 17 00:00:00 2001 From: Niilo Minkkinen <ext-niilo.1.minkkinen@nokia.com> Date: Mon, 18 May 2009 17:54:16 +0300 Subject: usb: musb: disable OTG AUTOIDLE only with omap3430 Omap3 MUSB AUTOIDLE functionality configured through OTG_SYSCONFIG register prevents the device from going into retention. This is a workaround (by Richard Woodruff/TI), as his comment : > A new MUSB bug which is a match to data below was identified very > recently (on hardware and in simulation). > This bug is in 3430 and not 3630. > As a priority test (and as new default) you should have engineers > disable autoidle for MUSB block. > This is the workaround which will show up in next errata. Signed-off-by: Niilo Minkkinen <ext-niilo.1.minkkinen@nokia.com> Signed-off-by: Richard Woodruff <r-woodruff2@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/musb/omap2430.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index 48930f25c450..34875201ee04 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -227,7 +227,12 @@ int __init musb_platform_init(struct musb *musb) l &= ~AUTOIDLE; /* disable auto idle */ l &= ~NOIDLE; /* remove possible noidle */ l |= SMARTIDLE; /* enable smart idle */ - l |= AUTOIDLE; /* enable auto idle */ + /* + * MUSB AUTOIDLE don't work in 3430. + * Workaround by Richard Woodruff/TI + */ + if (!cpu_is_omap3430()) + l |= AUTOIDLE; /* enable auto idle */ omap_writel(l, OTG_SYSCONFIG); l = omap_readl(OTG_INTERFSEL); -- cgit v1.2.3 From be475d9027a07069407a19980d1a4fcb7b18b23c Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Thu, 21 May 2009 17:37:58 -0400 Subject: USB: usb-storage: fix return values from init functions This patch (as1242) fixes the return values from the special init functions in usb-storage. They are supposed to return 0 for success, not USB_STOR_TRANSPORT_GOOD. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/storage/option_ms.c | 8 ++++---- drivers/usb/storage/sierra_ms.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/storage/option_ms.c b/drivers/usb/storage/option_ms.c index 126ea3424c03..0d8853339bb6 100644 --- a/drivers/usb/storage/option_ms.c +++ b/drivers/usb/storage/option_ms.c @@ -96,7 +96,7 @@ int option_ms_init(struct us_data *us) udev->descriptor.bDeviceSubClass != 0 || udev->descriptor.bDeviceProtocol != 0 || udev->actconfig->desc.bNumInterfaces == 3) - return USB_STOR_TRANSPORT_GOOD; + return 0; US_DEBUGP("Option MS: option_ms_init called\n"); @@ -107,7 +107,7 @@ int option_ms_init(struct us_data *us) iface_desc->desc.bInterfaceProtocol != 0x50) { US_DEBUGP("Option MS: mass storage interface not found, no action " "required\n"); - return USB_STOR_TRANSPORT_GOOD; + return 0; } /* Find the mass storage bulk endpoints */ @@ -127,7 +127,7 @@ int option_ms_init(struct us_data *us) if (!ep_in_size || !ep_out_size) { US_DEBUGP("Option MS: mass storage endpoints not found, no action " "required\n"); - return USB_STOR_TRANSPORT_GOOD; + return 0; } /* Force Modem mode */ @@ -143,6 +143,6 @@ int option_ms_init(struct us_data *us) " requests it\n"); } - return USB_STOR_TRANSPORT_GOOD; + return 0; } diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c index 4359a2cb42df..4395c4100ec2 100644 --- a/drivers/usb/storage/sierra_ms.c +++ b/drivers/usb/storage/sierra_ms.c @@ -202,6 +202,6 @@ int sierra_ms_init(struct us_data *us) complete: result = device_create_file(&us->pusb_intf->dev, &dev_attr_truinst); - return USB_STOR_TRANSPORT_GOOD; + return 0; } -- cgit v1.2.3 From 68335e816a92411649955a9903b1f30c388ea322 Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Fri, 22 May 2009 17:02:33 -0400 Subject: USB: EHCI: stagger frames for interrupt transfers This patch (as1243) tries to improve ehci-hcd's scheduling of interrupt transfers. Instead of trying to cram all transfers with the same period into the same frame, the new code will spread the transfers out among lots of different frames. This should reduce the periodic schedule load in any one frame -- some host controllers have trouble when there's too much work to do. A more thorough approach would stagger the uframe values as well. But this is enough to make a big improvement. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Tested-by: Dwayne Fontenot <dwayne.fontenot@att.net> Cc: David Brownell <david-b@pacbell.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-sched.c | 8 +++++--- drivers/usb/host/ehci.h | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 556d0ec0c1f8..9d1babc7ff65 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -760,8 +760,10 @@ static int qh_schedule(struct ehci_hcd *ehci, struct ehci_qh *qh) if (status) { /* "normal" case, uframing flexible except with splits */ if (qh->period) { - frame = qh->period - 1; - do { + int i; + + for (i = qh->period; status && i > 0; --i) { + frame = ++ehci->random_frame % qh->period; for (uframe = 0; uframe < 8; uframe++) { status = check_intr_schedule (ehci, frame, uframe, qh, @@ -769,7 +771,7 @@ static int qh_schedule(struct ehci_hcd *ehci, struct ehci_qh *qh) if (status == 0) break; } - } while (status && frame--); + } /* qh->period == 0 means every uframe */ } else { diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 6cff195e1a36..90ad3395bb21 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -116,6 +116,7 @@ struct ehci_hcd { /* one per controller */ struct timer_list watchdog; unsigned long actions; unsigned stamp; + unsigned random_frame; unsigned long next_statechange; u32 command; -- cgit v1.2.3 From 32ebbe7b6ad44ae9c276419710b56de6ba705303 Mon Sep 17 00:00:00 2001 From: Josua Dietze <digidietze@draisberghof.de> Date: Sun, 24 May 2009 23:21:42 +0200 Subject: USB: usb-storage: add filter to "option_ms" to leave unrecognized devices alone Some unusual usb devices from the maker "Option" are switched from storage to serial/modem mode by sending a SCSI REZERO command. In one case a fairly common vendor/device ID is affected which led to problems for users of other modems or phones which are not supposed to be switched. The patch adds a filter by reading the vendor name with the SCSI INQUIRY command, and skips the switching code for all unrecognized entries. Further changes are cleanups and corrections pointed out by Alan Stern. Tested with two devices with the IDs 05c6:1000, one from "Option" and switchable, and one from Samsung (cell phone). Signed-off-by: Josua Dietze <digidietze@draisberghof.de> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/storage/option_ms.c | 121 +++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 52 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/storage/option_ms.c b/drivers/usb/storage/option_ms.c index 0d8853339bb6..d41cc0a970f7 100644 --- a/drivers/usb/storage/option_ms.c +++ b/drivers/usb/storage/option_ms.c @@ -37,7 +37,7 @@ MODULE_PARM_DESC(option_zero_cd, "ZeroCD mode (1=Force Modem (default)," #define RESPONSE_LEN 1024 -static int option_rezero(struct us_data *us, int ep_in, int ep_out) +static int option_rezero(struct us_data *us) { const unsigned char rezero_msg[] = { 0x55, 0x53, 0x42, 0x43, 0x78, 0x56, 0x34, 0x12, @@ -54,10 +54,10 @@ static int option_rezero(struct us_data *us, int ep_in, int ep_out) if (buffer == NULL) return USB_STOR_TRANSPORT_ERROR; - memcpy(buffer, rezero_msg, sizeof (rezero_msg)); + memcpy(buffer, rezero_msg, sizeof(rezero_msg)); result = usb_stor_bulk_transfer_buf(us, - usb_sndbulkpipe(us->pusb_dev, ep_out), - buffer, sizeof (rezero_msg), NULL); + us->send_bulk_pipe, + buffer, sizeof(rezero_msg), NULL); if (result != USB_STOR_XFER_GOOD) { result = USB_STOR_XFER_ERROR; goto out; @@ -66,9 +66,15 @@ static int option_rezero(struct us_data *us, int ep_in, int ep_out) /* Some of the devices need to be asked for a response, but we don't * care what that response is. */ - result = usb_stor_bulk_transfer_buf(us, - usb_sndbulkpipe(us->pusb_dev, ep_out), + usb_stor_bulk_transfer_buf(us, + us->recv_bulk_pipe, buffer, RESPONSE_LEN, NULL); + + /* Read the CSW */ + usb_stor_bulk_transfer_buf(us, + us->recv_bulk_pipe, + buffer, 13, NULL); + result = USB_STOR_XFER_GOOD; out: @@ -76,64 +82,75 @@ out: return result; } -int option_ms_init(struct us_data *us) +static int option_inquiry(struct us_data *us) { - struct usb_device *udev; - struct usb_interface *intf; - struct usb_host_interface *iface_desc; - struct usb_endpoint_descriptor *endpoint = NULL; - u8 ep_in = 0, ep_out = 0; - int ep_in_size = 0, ep_out_size = 0; - int i, result; - - udev = us->pusb_dev; - intf = us->pusb_intf; - - /* Ensure it's really a ZeroCD device; devices that are already - * in modem mode return 0xFF for class, subclass, and protocol. - */ - if (udev->descriptor.bDeviceClass != 0 || - udev->descriptor.bDeviceSubClass != 0 || - udev->descriptor.bDeviceProtocol != 0 || - udev->actconfig->desc.bNumInterfaces == 3) - return 0; + const unsigned char inquiry_msg[] = { + 0x55, 0x53, 0x42, 0x43, 0x12, 0x34, 0x56, 0x78, + 0x24, 0x00, 0x00, 0x00, 0x80, 0x00, 0x06, 0x12, + 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + char *buffer; + int result; - US_DEBUGP("Option MS: option_ms_init called\n"); + US_DEBUGP("Option MS: %s", "device inquiry for vendor name\n"); - /* Find the right mass storage interface */ - iface_desc = intf->cur_altsetting; - if (iface_desc->desc.bInterfaceClass != 0x8 || - iface_desc->desc.bInterfaceSubClass != 0x6 || - iface_desc->desc.bInterfaceProtocol != 0x50) { - US_DEBUGP("Option MS: mass storage interface not found, no action " - "required\n"); - return 0; + buffer = kzalloc(0x24, GFP_KERNEL); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + + memcpy(buffer, inquiry_msg, sizeof(inquiry_msg)); + result = usb_stor_bulk_transfer_buf(us, + us->send_bulk_pipe, + buffer, sizeof(inquiry_msg), NULL); + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_XFER_ERROR; + goto out; } - /* Find the mass storage bulk endpoints */ - for (i = 0; i < iface_desc->desc.bNumEndpoints && (!ep_in_size || !ep_out_size); ++i) { - endpoint = &iface_desc->endpoint[i].desc; - - if (usb_endpoint_is_bulk_in(endpoint)) { - ep_in = usb_endpoint_num(endpoint); - ep_in_size = le16_to_cpu(endpoint->wMaxPacketSize); - } else if (usb_endpoint_is_bulk_out(endpoint)) { - ep_out = usb_endpoint_num(endpoint); - ep_out_size = le16_to_cpu(endpoint->wMaxPacketSize); - } + result = usb_stor_bulk_transfer_buf(us, + us->recv_bulk_pipe, + buffer, 0x24, NULL); + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_XFER_ERROR; + goto out; } - /* Can't find the mass storage endpoints */ - if (!ep_in_size || !ep_out_size) { - US_DEBUGP("Option MS: mass storage endpoints not found, no action " - "required\n"); + result = memcmp(buffer+8, "Option", 6); + + /* Read the CSW */ + usb_stor_bulk_transfer_buf(us, + us->recv_bulk_pipe, + buffer, 13, NULL); + +out: + kfree(buffer); + return result; +} + + +int option_ms_init(struct us_data *us) +{ + int result; + + US_DEBUGP("Option MS: option_ms_init called\n"); + + /* Additional test for vendor information via INQUIRY, + * because some vendor/product IDs are ambiguous + */ + result = option_inquiry(us); + if (result != 0) { + US_DEBUGP("Option MS: vendor is not Option or not determinable," + " no action taken\n"); return 0; - } + } else + US_DEBUGP("Option MS: this is a genuine Option device," + " proceeding\n"); /* Force Modem mode */ if (option_zero_cd == ZCD_FORCE_MODEM) { US_DEBUGP("Option MS: %s", "Forcing Modem Mode\n"); - result = option_rezero(us, ep_in, ep_out); + result = option_rezero(us); if (result != USB_STOR_XFER_GOOD) US_DEBUGP("Option MS: Failed to switch to modem mode.\n"); return -EIO; -- cgit v1.2.3 From 5effabbe9e6e0089f7afdde35cb51e8c8b4cf6bc Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> Date: Tue, 26 May 2009 18:24:34 +0900 Subject: USB: r8a66597-hcd: use platform_data instead of module_param CPU/board specific parameters (PLL clock, vif etc...) can be set by platform_data instead of module_param. v2: remove irq_sense member in platform_data because it can OR in IRQF_TRIGGER_LOW or IRQF_TRIGGER_FALLING against IORESOURCE_IRQ in the struct resource. Signed-off-by: Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> Reviewed-by: Paul Mundt <lethal@linux-sh.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/r8a66597-hcd.c | 60 ++++++++++------------------------------- drivers/usb/host/r8a66597.h | 38 +++++++++++++++++++++++--- include/linux/usb/r8a66597.h | 44 ++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 50 deletions(-) create mode 100644 include/linux/usb/r8a66597.h (limited to 'drivers') diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index 3e1216ad86bd..56976cc0352a 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -46,31 +46,10 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Yoshihiro Shimoda"); MODULE_ALIAS("platform:r8a66597_hcd"); -#define DRIVER_VERSION "10 Apr 2008" +#define DRIVER_VERSION "2009-05-26" static const char hcd_name[] = "r8a66597_hcd"; -/* module parameters */ -#if !defined(CONFIG_SUPERH_ON_CHIP_R8A66597) -static unsigned short clock = XTAL12; -module_param(clock, ushort, 0644); -MODULE_PARM_DESC(clock, "input clock: 48MHz=32768, 24MHz=16384, 12MHz=0 " - "(default=0)"); -#endif - -static unsigned short vif = LDRV; -module_param(vif, ushort, 0644); -MODULE_PARM_DESC(vif, "input VIF: 3.3V=32768, 1.5V=0(default=32768)"); - -static unsigned short endian; -module_param(endian, ushort, 0644); -MODULE_PARM_DESC(endian, "data endian: big=256, little=0 (default=0)"); - -static unsigned short irq_sense = 0xff; -module_param(irq_sense, ushort, 0644); -MODULE_PARM_DESC(irq_sense, "IRQ sense: low level=32, falling edge=0 " - "(default=32)"); - static void packet_write(struct r8a66597 *r8a66597, u16 pipenum); static int r8a66597_get_frame(struct usb_hcd *hcd); @@ -136,7 +115,8 @@ static int r8a66597_clock_enable(struct r8a66597 *r8a66597) } } while ((tmp & USBE) != USBE); r8a66597_bclr(r8a66597, USBE, SYSCFG0); - r8a66597_mdfy(r8a66597, clock, XTAL, SYSCFG0); + r8a66597_mdfy(r8a66597, get_xtal_from_pdata(r8a66597->pdata), XTAL, + SYSCFG0); i = 0; r8a66597_bset(r8a66597, XCKE, SYSCFG0); @@ -203,6 +183,9 @@ static void r8a66597_disable_port(struct r8a66597 *r8a66597, int port) static int enable_controller(struct r8a66597 *r8a66597) { int ret, port; + u16 vif = r8a66597->pdata->vif ? LDRV : 0; + u16 irq_sense = r8a66597->irq_sense_low ? INTL : 0; + u16 endian = r8a66597->pdata->endian ? BIGEND : 0; ret = r8a66597_clock_enable(r8a66597); if (ret < 0) @@ -2418,6 +2401,12 @@ static int __devinit r8a66597_probe(struct platform_device *pdev) goto clean_up; } + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform data\n"); + ret = -ENODEV; + goto clean_up; + } + /* initialize hcd */ hcd = usb_create_hcd(&r8a66597_hc_driver, &pdev->dev, (char *)hcd_name); if (!hcd) { @@ -2428,6 +2417,8 @@ static int __devinit r8a66597_probe(struct platform_device *pdev) r8a66597 = hcd_to_r8a66597(hcd); memset(r8a66597, 0, sizeof(struct r8a66597)); dev_set_drvdata(&pdev->dev, r8a66597); + r8a66597->pdata = pdev->dev.platform_data; + r8a66597->irq_sense_low = irq_trigger == IRQF_TRIGGER_LOW; #if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK) snprintf(clk_name, sizeof(clk_name), "usb%d", pdev->id); @@ -2458,29 +2449,6 @@ static int __devinit r8a66597_probe(struct platform_device *pdev) hcd->rsrc_start = res->start; - /* irq_sense setting on cmdline takes precedence over resource - * settings, so the introduction of irqflags in IRQ resourse - * won't disturb existing setups */ - switch (irq_sense) { - case INTL: - irq_trigger = IRQF_TRIGGER_LOW; - break; - case 0: - irq_trigger = IRQF_TRIGGER_FALLING; - break; - case 0xff: - if (irq_trigger) - irq_sense = (irq_trigger & IRQF_TRIGGER_LOW) ? - INTL : 0; - else { - irq_sense = INTL; - irq_trigger = IRQF_TRIGGER_LOW; - } - break; - default: - dev_err(&pdev->dev, "Unknown irq_sense value.\n"); - } - ret = usb_add_hcd(hcd, irq, IRQF_DISABLED | irq_trigger); if (ret != 0) { dev_err(&pdev->dev, "Failed to add hcd\n"); diff --git a/drivers/usb/host/r8a66597.h b/drivers/usb/host/r8a66597.h index f49208f1bb74..d72680b433f9 100644 --- a/drivers/usb/host/r8a66597.h +++ b/drivers/usb/host/r8a66597.h @@ -30,6 +30,8 @@ #include <linux/clk.h> #endif +#include <linux/usb/r8a66597.h> + #define SYSCFG0 0x00 #define SYSCFG1 0x02 #define SYSSTS0 0x04 @@ -488,6 +490,7 @@ struct r8a66597 { #if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK) struct clk *clk; #endif + struct r8a66597_platdata *pdata; struct r8a66597_device device0; struct r8a66597_root_hub root_hub[R8A66597_MAX_ROOT_HUB]; struct list_head pipe_queue[R8A66597_MAX_NUM_PIPE]; @@ -506,6 +509,7 @@ struct r8a66597 { unsigned long child_connect_map[4]; unsigned bus_suspended:1; + unsigned irq_sense_low:1; }; static inline struct r8a66597 *hcd_to_r8a66597(struct usb_hcd *hcd) @@ -660,10 +664,36 @@ static inline void r8a66597_port_power(struct r8a66597 *r8a66597, int port, { unsigned long dvstctr_reg = get_dvstctr_reg(port); - if (power) - r8a66597_bset(r8a66597, VBOUT, dvstctr_reg); - else - r8a66597_bclr(r8a66597, VBOUT, dvstctr_reg); + if (r8a66597->pdata->port_power) { + r8a66597->pdata->port_power(port, power); + } else { + if (power) + r8a66597_bset(r8a66597, VBOUT, dvstctr_reg); + else + r8a66597_bclr(r8a66597, VBOUT, dvstctr_reg); + } +} + +static inline u16 get_xtal_from_pdata(struct r8a66597_platdata *pdata) +{ + u16 clock = 0; + + switch (pdata->xtal) { + case R8A66597_PLATDATA_XTAL_12MHZ: + clock = XTAL12; + break; + case R8A66597_PLATDATA_XTAL_24MHZ: + clock = XTAL24; + break; + case R8A66597_PLATDATA_XTAL_48MHZ: + clock = XTAL48; + break; + default: + printk(KERN_ERR "r8a66597: platdata clock is wrong.\n"); + break; + } + + return clock; } #define get_pipectr_addr(pipenum) (PIPE1CTR + (pipenum - 1) * 2) diff --git a/include/linux/usb/r8a66597.h b/include/linux/usb/r8a66597.h new file mode 100644 index 000000000000..e9f0384fa20c --- /dev/null +++ b/include/linux/usb/r8a66597.h @@ -0,0 +1,44 @@ +/* + * R8A66597 driver platform data + * + * Copyright (C) 2009 Renesas Solutions Corp. + * + * Author : Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> + * + * 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; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __LINUX_USB_R8A66597_H +#define __LINUX_USB_R8A66597_H + +#define R8A66597_PLATDATA_XTAL_12MHZ 0x01 +#define R8A66597_PLATDATA_XTAL_24MHZ 0x02 +#define R8A66597_PLATDATA_XTAL_48MHZ 0x03 + +struct r8a66597_platdata { + /* This ops can controll port power instead of DVSTCTR register. */ + void (*port_power)(int port, int power); + + /* (external controller only) set R8A66597_PLATDATA_XTAL_nnMHZ */ + unsigned xtal:2; + + /* set one = 3.3V, set zero = 1.5V */ + unsigned vif:1; + + /* set one = big endian, set zero = little endian */ + unsigned endian:1; +}; +#endif + -- cgit v1.2.3 From b18ffd49e86102a9ed0a1cc83fdafe3891e844e5 Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Wed, 27 May 2009 18:21:56 -0400 Subject: USB: EHCI: update toggle state for linked QHs This patch (as1245) fixes a bug in ehci-hcd. When an URB is queued for an endpoint whose QH is already in the LINKED state, the QH doesn't get refreshed. As a result, if usb_clear_halt() was called during the time that the QH was linked but idle, the data toggle value in the QH doesn't get reset. The symptom is that after a clear_halt, data gets lost and transfers time out. This problem is starting to show up now because the "ehci-hcd unlink speedups" patch causes QHs with no queued URBs to remain linked for a suitable time. The patch utilizes the new endpoint_reset mechanism to fix the problem. When an endpoint is reset, the new method forcibly unlinks the QH (if necessary) and safely updates the toggle value. This allows qh_update() to be simplified and avoids using usb_device's toggle bits in a rather unintuitive way. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> CC: David Brownell <david-b@pacbell.net> Tested-by: David <david@unsolicited.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-au1xxx.c | 1 + drivers/usb/host/ehci-fsl.c | 1 + drivers/usb/host/ehci-hcd.c | 45 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/ehci-ixp4xx.c | 1 + drivers/usb/host/ehci-orion.c | 1 + drivers/usb/host/ehci-pci.c | 1 + drivers/usb/host/ehci-ppc-of.c | 1 + drivers/usb/host/ehci-ps3.c | 1 + drivers/usb/host/ehci-q.c | 19 +----------------- 9 files changed, 53 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-au1xxx.c b/drivers/usb/host/ehci-au1xxx.c index bf69f4739107..c3a778bd359c 100644 --- a/drivers/usb/host/ehci-au1xxx.c +++ b/drivers/usb/host/ehci-au1xxx.c @@ -97,6 +97,7 @@ static const struct hc_driver ehci_au1xxx_hc_driver = { .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, /* * scheduling support diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index 01c3da34f678..bf86809c5120 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -309,6 +309,7 @@ static const struct hc_driver ehci_fsl_hc_driver = { .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, /* * scheduling support diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index a2ca9cbf2809..2b72473544d3 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1024,6 +1024,51 @@ done: return; } +static void +ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct ehci_qh *qh; + int eptype = usb_endpoint_type(&ep->desc); + + if (eptype != USB_ENDPOINT_XFER_BULK && eptype != USB_ENDPOINT_XFER_INT) + return; + + rescan: + spin_lock_irq(&ehci->lock); + qh = ep->hcpriv; + + /* For Bulk and Interrupt endpoints we maintain the toggle state + * in the hardware; the toggle bits in udev aren't used at all. + * When an endpoint is reset by usb_clear_halt() we must reset + * the toggle bit in the QH. + */ + if (qh) { + if (!list_empty(&qh->qtd_list)) { + WARN_ONCE(1, "clear_halt for a busy endpoint\n"); + } else if (qh->qh_state == QH_STATE_IDLE) { + qh->hw_token &= ~cpu_to_hc32(ehci, QTD_TOGGLE); + } else { + /* It's not safe to write into the overlay area + * while the QH is active. Unlink it first and + * wait for the unlink to complete. + */ + if (qh->qh_state == QH_STATE_LINKED) { + if (eptype == USB_ENDPOINT_XFER_BULK) { + unlink_async(ehci, qh); + } else { + intr_deschedule(ehci, qh); + (void) qh_schedule(ehci, qh); + } + } + spin_unlock_irq(&ehci->lock); + schedule_timeout_uninterruptible(1); + goto rescan; + } + } + spin_unlock_irq(&ehci->lock); +} + static int ehci_get_frame (struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); diff --git a/drivers/usb/host/ehci-ixp4xx.c b/drivers/usb/host/ehci-ixp4xx.c index 9c32063a0c2f..a44bb4a94954 100644 --- a/drivers/usb/host/ehci-ixp4xx.c +++ b/drivers/usb/host/ehci-ixp4xx.c @@ -51,6 +51,7 @@ static const struct hc_driver ixp4xx_ehci_hc_driver = { .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, .get_frame_number = ehci_get_frame, .hub_status_data = ehci_hub_status_data, .hub_control = ehci_hub_control, diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c index 17dc15407a07..770dd9aba62a 100644 --- a/drivers/usb/host/ehci-orion.c +++ b/drivers/usb/host/ehci-orion.c @@ -149,6 +149,7 @@ static const struct hc_driver ehci_orion_hc_driver = { .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, /* * scheduling support diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index e74948898f76..f3683e1da161 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -388,6 +388,7 @@ static const struct hc_driver ehci_pci_hc_driver = { .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, /* * scheduling support diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index ef732b704f53..fbd272288fc2 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -61,6 +61,7 @@ static const struct hc_driver ehci_ppc_of_hc_driver = { .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, /* * scheduling support diff --git a/drivers/usb/host/ehci-ps3.c b/drivers/usb/host/ehci-ps3.c index 1ba9f9a8c308..4b4df23efddf 100644 --- a/drivers/usb/host/ehci-ps3.c +++ b/drivers/usb/host/ehci-ps3.c @@ -65,6 +65,7 @@ static const struct hc_driver ps3_ehci_hc_driver = { .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, .get_frame_number = ehci_get_frame, .hub_status_data = ehci_hub_status_data, .hub_control = ehci_hub_control, diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 1976b1b3778c..3192f683f807 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -93,22 +93,6 @@ qh_update (struct ehci_hcd *ehci, struct ehci_qh *qh, struct ehci_qtd *qtd) qh->hw_qtd_next = QTD_NEXT(ehci, qtd->qtd_dma); qh->hw_alt_next = EHCI_LIST_END(ehci); - /* Except for control endpoints, we make hardware maintain data - * toggle (like OHCI) ... here (re)initialize the toggle in the QH, - * and set the pseudo-toggle in udev. Only usb_clear_halt() will - * ever clear it. - */ - if (!(qh->hw_info1 & cpu_to_hc32(ehci, 1 << 14))) { - unsigned is_out, epnum; - - is_out = !(qtd->hw_token & cpu_to_hc32(ehci, 1 << 8)); - epnum = (hc32_to_cpup(ehci, &qh->hw_info1) >> 8) & 0x0f; - if (unlikely (!usb_gettoggle (qh->dev, epnum, is_out))) { - qh->hw_token &= ~cpu_to_hc32(ehci, QTD_TOGGLE); - usb_settoggle (qh->dev, epnum, is_out, 1); - } - } - /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ wmb (); qh->hw_token &= cpu_to_hc32(ehci, QTD_TOGGLE | QTD_STS_PING); @@ -850,7 +834,6 @@ done: qh->qh_state = QH_STATE_IDLE; qh->hw_info1 = cpu_to_hc32(ehci, info1); qh->hw_info2 = cpu_to_hc32(ehci, info2); - usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input, 1); qh_refresh (ehci, qh); return qh; } @@ -881,7 +864,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) } } - /* clear halt and/or toggle; and maybe recover from silicon quirk */ + /* clear halt and maybe recover from silicon quirk */ if (qh->qh_state == QH_STATE_IDLE) qh_refresh (ehci, qh); -- cgit v1.2.3 From 9b8e7ba68ad0e4273f4897950de65bc311552cd1 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> Date: Thu, 28 May 2009 10:10:44 +0900 Subject: USB: replace dma_sync_single and dma_sync_sg with dma_sync_single_for_cpu and dma_sync_sg_for_cpu This replaces dma_sync_single() and dma_sync_sg() with dma_sync_single_for_cpu() and dma_sync_sg_for_cpu() respectively because they is an obsolete API; include/linux/dma-mapping.h says: /* Backwards compat, remove in 2.7.x */ #define dma_sync_single dma_sync_single_for_cpu #define dma_sync_sg dma_sync_sg_for_cpu Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/usb.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index c32811ae128f..020b58528d90 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -799,12 +799,12 @@ void usb_buffer_dmasync(struct urb *urb) return; if (controller->dma_mask) { - dma_sync_single(controller, + dma_sync_single_for_cpu(controller, urb->transfer_dma, urb->transfer_buffer_length, usb_pipein(urb->pipe) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); if (usb_pipecontrol(urb->pipe)) - dma_sync_single(controller, + dma_sync_single_for_cpu(controller, urb->setup_dma, sizeof(struct usb_ctrlrequest), DMA_TO_DEVICE); @@ -922,8 +922,8 @@ void usb_buffer_dmasync_sg(const struct usb_device *dev, int is_in, || !controller->dma_mask) return; - dma_sync_sg(controller, sg, n_hw_ents, - is_in ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + dma_sync_sg_for_cpu(controller, sg, n_hw_ents, + is_in ? DMA_FROM_DEVICE : DMA_TO_DEVICE); } EXPORT_SYMBOL_GPL(usb_buffer_dmasync_sg); #endif -- cgit v1.2.3 From 2e25134122c25ebb0679b4bbd536fb46c669f9d7 Mon Sep 17 00:00:00 2001 From: Daniel Glöckner <dg@emlix.com> Date: Thu, 28 May 2009 12:53:24 +0200 Subject: USB: gadget: g_serial: append zlp when tx buffer becomes empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some usb serial host drivers expect a short packet before they forward the data to the application. This is caused by them trying to read more than one packet at a time. So when the gadget sends an exact multiple of the maximum packet size, it should append a zero-length packet. Signed-off-by: Daniel Glöckner <dg@emlix.com> Cc: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/u_serial.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c index 0a4d99ab40d8..fc6e709f45b1 100644 --- a/drivers/usb/gadget/u_serial.c +++ b/drivers/usb/gadget/u_serial.c @@ -371,6 +371,7 @@ __acquires(&port->port_lock) req->length = len; list_del(&req->list); + req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0); pr_vdebug(PREFIX "%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n", port->port_num, len, *((u8 *)req->buf), -- cgit v1.2.3 From 680cc64557101eaaca706dc9a1a0777f35aac0da Mon Sep 17 00:00:00 2001 From: Daniel Glöckner <dg@emlix.com> Date: Thu, 28 May 2009 13:00:14 +0200 Subject: USB: gadget: imx_udc: don't queue more data when zlp is to be sent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a zero-length packet has been requested and another packet is written into the fifo, the MX1 tends to send the first byte of the previous packet instead of the first byte of the current packet. The CRC is adjusted accordingly so that this packet is _not_ discarded by the host. Waiting for the ZLPS bit to clear avoids these bad packets. Signed-off-by: Daniel Glöckner <dg@emlix.com> Cc: Darius Augulis <augulis.darius@gmail.com> Cc: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/imx_udc.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/imx_udc.c b/drivers/usb/gadget/imx_udc.c index 239bf8ed9f4a..c52a681f376c 100644 --- a/drivers/usb/gadget/imx_udc.c +++ b/drivers/usb/gadget/imx_udc.c @@ -415,6 +415,13 @@ static int write_packet(struct imx_ep_struct *imx_ep, struct imx_request *req) u8 *buf; int length, count, temp; + if (unlikely(__raw_readl(imx_ep->imx_usb->base + + USB_EP_STAT(EP_NO(imx_ep))) & EPSTAT_ZLPS)) { + D_TRX(imx_ep->imx_usb->dev, "<%s> zlp still queued in EP %s\n", + __func__, imx_ep->ep.name); + return -1; + } + buf = req->req.buf + req->req.actual; prefetch(buf); -- cgit v1.2.3 From 1f17c5026ce27d0449903d34f9fca461a45fe1cb Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin <kir@openvz.org> Date: Thu, 28 May 2009 20:33:58 +0400 Subject: USB: cdc-acm: quirk for Alcatel OT-I650 This mobile phone fails to work as a modem, failing with: cdc_acm: Zero length descriptor references cdc_acm: probe of 1-6.1.3:1.1 failed with error -22 Tested to work fine with this patch. Signed-off-by: Kir Kolyshkin <kir@openvz.org> Cc: stable <stable@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/class/cdc-acm.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 778e023d1467..38bfdb0f6660 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1495,6 +1495,9 @@ static struct usb_device_id acm_ids[] = { Maybe we should define a new quirk for this. */ }, + { USB_DEVICE(0x1bbb, 0x0003), /* Alcatel OT-I650 */ + .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */ + }, /* control interfaces with various AT-command sets */ { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, -- cgit v1.2.3 From 830d1b188c997c4af094d4e20b194205ddbded13 Mon Sep 17 00:00:00 2001 From: Maulik Mankad <x0082077@ti.com> Date: Fri, 29 May 2009 18:34:40 +0530 Subject: USB: gadget : Fix RNDIS code to pass USB Compliance tests (USBCV) with g_ether This patch fixes a bug in the RNDIS code. Due to this bug gether_connect() fails as the port remains un-initialized. As a result following USB Compliance Tests were failing. (1)EndpointDescriptorTest_DeviceConfigured (2)Interface Descriptor Test. (3)Halt Endpoint Test. (4)SetConfigurationTest The fix aligns rndis code with the CDC ECM for xxx_set_alt(). The above listed USB Compliance test passes with this fix. Tested working fine on SDP with OMAP 3430. Signed-off-by: Maulik Mankad <x0082077@ti.com> CC: David Brownell <david-b@pacbell.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/f_rndis.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index 3279a4726042..424a37c5773f 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -475,7 +475,9 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (rndis->port.in_ep->driver_data) { DBG(cdev, "reset rndis\n"); gether_disconnect(&rndis->port); - } else { + } + + if (!rndis->port.in) { DBG(cdev, "init rndis\n"); rndis->port.in = ep_choose(cdev->gadget, rndis->hs.in, rndis->fs.in); -- cgit v1.2.3 From 430eb0d27c1b36c5191c16b2472b26137673a8d4 Mon Sep 17 00:00:00 2001 From: Jason Wessel <jason.wessel@windriver.com> Date: Fri, 29 May 2009 13:34:16 -0500 Subject: USB: pl2303 usb_serial: implement sysrq handling on break Add callbacks to process the sysrq when using a pl2303 usb device as a console. Signed-off-by: Jason Wessel <jason.wessel@windriver.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/pl2303.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index e02dc3d643c7..6357b57f628c 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -927,6 +927,8 @@ static void pl2303_update_line_status(struct usb_serial_port *port, spin_lock_irqsave(&priv->lock, flags); priv->line_status = data[status_idx]; spin_unlock_irqrestore(&priv->lock, flags); + if (priv->line_status & UART_BREAK_ERROR) + usb_serial_handle_break(port); wake_up_interruptible(&priv->delta_msr_wait); } @@ -1037,7 +1039,8 @@ static void pl2303_read_bulk_callback(struct urb *urb) if (line_status & UART_OVERRUN_ERROR) tty_insert_flip_char(tty, 0, TTY_OVERRUN); for (i = 0; i < urb->actual_length; ++i) - tty_insert_flip_char(tty, data[i], tty_flag); + if (!usb_serial_handle_sysrq_char(port, data[i])) + tty_insert_flip_char(tty, data[i], tty_flag); tty_flip_buffer_push(tty); } tty_kref_put(tty); -- cgit v1.2.3 From 568d422e9cf52b7b26d2e026ae1617971f62b560 Mon Sep 17 00:00:00 2001 From: Jason Wessel <jason.wessel@windriver.com> Date: Fri, 29 May 2009 13:34:17 -0500 Subject: USB: usb_serial: only allow sysrq on a console port The only time a sysrq should get processed is if the attached device is a console. This is intended to protect sysrq execution on a host connected with a terminal program. Here is the problem scenario: host A <-- rs232 link --> host B Host A is using mincom and a usb pl2303 device to connect to host b which is a linux system with a usb pl2303 device acting as the serial console. When host B is rebooted the pl2303 emits random junk characters on reset. These character sequences contain serial break signals most of the time and when translated to a sysrq have caused host A to get random processes killed, reboots or power down. It is true that in this setup with this patch host B might still have the same problem as host A if you reboot host A. In most cases host A is a development host which seldom gets rebooted, and you could turn off sysrq temporarily on host B if you need to reboot host A. Signed-off-by: Jason Wessel <jason.wessel@windriver.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/generic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 9fccc26235a2..8dabac1929b0 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -528,7 +528,7 @@ void usb_serial_generic_unthrottle(struct tty_struct *tty) int usb_serial_handle_sysrq_char(struct usb_serial_port *port, unsigned int ch) { - if (port->sysrq) { + if (port->sysrq && port->console) { if (ch && time_before(jiffies, port->sysrq)) { handle_sysrq(ch, tty_port_tty_get(&port->port)); port->sysrq = 0; -- cgit v1.2.3 From 54e4026b64a970303349b952866641a7804ef594 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski <lg@denx.de> Date: Wed, 15 Apr 2009 14:25:33 +0200 Subject: USB: gadget: Add i.MX3x support to the fsl_usb2_udc driver This patch adds support for i.MX3x (only tested with i.MX31 so far) ARM SoCs to the fsl_usb2_udc driver. It also moves PHY configuration before controller reset, because otherwise an ULPI PHY doesn't get a reset and doesn't function after a reboot. The problem with longer control transfers is still not fixed. The patch renames the fsl_usb2_udc.c file to fsl_udc_core.c to preserve the same module name for user-space backwards compatibility. Signed-off-by: Guennadi Liakhovetski <lg@denx.de> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/Kconfig | 2 +- drivers/usb/gadget/Makefile | 4 + drivers/usb/gadget/fsl_mx3_udc.c | 95 ++ drivers/usb/gadget/fsl_udc_core.c | 2491 +++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/fsl_usb2_udc.c | 2468 ------------------------------------ drivers/usb/gadget/fsl_usb2_udc.h | 18 + 6 files changed, 2609 insertions(+), 2469 deletions(-) create mode 100644 drivers/usb/gadget/fsl_mx3_udc.c create mode 100644 drivers/usb/gadget/fsl_udc_core.c delete mode 100644 drivers/usb/gadget/fsl_usb2_udc.c (limited to 'drivers') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 772dd3b9bab9..5de9b4f21713 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -156,7 +156,7 @@ config USB_ATMEL_USBA config USB_GADGET_FSL_USB2 boolean "Freescale Highspeed USB DR Peripheral Controller" - depends on FSL_SOC + depends on FSL_SOC || ARCH_MXC select USB_GADGET_DUALSPEED help Some of Freescale PowerPC processors have a High Speed diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 39a51d746cb7..c3fe06396b74 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -18,6 +18,10 @@ obj-$(CONFIG_USB_S3C2410) += s3c2410_udc.o 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 +fsl_usb2_udc-objs := fsl_udc_core.o +ifeq ($(CONFIG_ARCH_MXC),y) +fsl_usb2_udc-objs += fsl_mx3_udc.o +endif obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o diff --git a/drivers/usb/gadget/fsl_mx3_udc.c b/drivers/usb/gadget/fsl_mx3_udc.c new file mode 100644 index 000000000000..4bc2bf3d602e --- /dev/null +++ b/drivers/usb/gadget/fsl_mx3_udc.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2009 + * Guennadi Liakhovetski, DENX Software Engineering, <lg@denx.de> + * + * Description: + * Helper routines for i.MX3x SoCs from Freescale, needed by the fsl_usb2_udc.c + * driver to function correctly on these systems. + * + * 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. + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/fsl_devices.h> +#include <linux/platform_device.h> + +static struct clk *mxc_ahb_clk; +static struct clk *mxc_usb_clk; + +int fsl_udc_clk_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + unsigned long freq; + int ret; + + pdata = pdev->dev.platform_data; + + mxc_ahb_clk = clk_get(&pdev->dev, "usb_ahb"); + if (IS_ERR(mxc_ahb_clk)) + return PTR_ERR(mxc_ahb_clk); + + ret = clk_enable(mxc_ahb_clk); + if (ret < 0) { + dev_err(&pdev->dev, "clk_enable(\"usb_ahb\") failed\n"); + goto eenahb; + } + + /* make sure USB_CLK is running at 60 MHz +/- 1000 Hz */ + mxc_usb_clk = clk_get(&pdev->dev, "usb"); + if (IS_ERR(mxc_usb_clk)) { + dev_err(&pdev->dev, "clk_get(\"usb\") failed\n"); + ret = PTR_ERR(mxc_usb_clk); + goto egusb; + } + + freq = clk_get_rate(mxc_usb_clk); + if (pdata->phy_mode != FSL_USB2_PHY_ULPI && + (freq < 59999000 || freq > 60001000)) { + dev_err(&pdev->dev, "USB_CLK=%lu, should be 60MHz\n", freq); + goto eclkrate; + } + + ret = clk_enable(mxc_usb_clk); + if (ret < 0) { + dev_err(&pdev->dev, "clk_enable(\"usb_clk\") failed\n"); + goto eenusb; + } + + return 0; + +eenusb: +eclkrate: + clk_put(mxc_usb_clk); + mxc_usb_clk = NULL; +egusb: + clk_disable(mxc_ahb_clk); +eenahb: + clk_put(mxc_ahb_clk); + return ret; +} + +void fsl_udc_clk_finalize(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + /* ULPI transceivers don't need usbpll */ + if (pdata->phy_mode == FSL_USB2_PHY_ULPI) { + clk_disable(mxc_usb_clk); + clk_put(mxc_usb_clk); + mxc_usb_clk = NULL; + } +} + +void fsl_udc_clk_release(void) +{ + if (mxc_usb_clk) { + clk_disable(mxc_usb_clk); + clk_put(mxc_usb_clk); + } + clk_disable(mxc_ahb_clk); + clk_put(mxc_ahb_clk); +} diff --git a/drivers/usb/gadget/fsl_udc_core.c b/drivers/usb/gadget/fsl_udc_core.c new file mode 100644 index 000000000000..42a74b8a0bb8 --- /dev/null +++ b/drivers/usb/gadget/fsl_udc_core.c @@ -0,0 +1,2491 @@ +/* + * Copyright (C) 2004-2007 Freescale Semicondutor, Inc. All rights reserved. + * + * Author: Li Yang <leoli@freescale.com> + * Jiang Bo <tanya.jiang@freescale.com> + * + * Description: + * Freescale high-speed USB SOC DR module device controller driver. + * This can be found on MPC8349E/MPC8313E cpus. + * The driver is previously named as mpc_udc. Based on bare board + * code from Dave Liu and Shlomi Gridish. + * + * 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 VERBOSE + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/mm.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/otg.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/dmapool.h> +#include <linux/delay.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/unaligned.h> +#include <asm/dma.h> + +#include "fsl_usb2_udc.h" + +#define DRIVER_DESC "Freescale High-Speed USB SOC Device Controller driver" +#define DRIVER_AUTHOR "Li Yang/Jiang Bo" +#define DRIVER_VERSION "Apr 20, 2007" + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +static const char driver_name[] = "fsl-usb2-udc"; +static const char driver_desc[] = DRIVER_DESC; + +static struct usb_dr_device *dr_regs; +#ifndef CONFIG_ARCH_MXC +static struct usb_sys_interface *usb_sys_regs; +#endif + +/* it is initialized in probe() */ +static struct fsl_udc *udc_controller = NULL; + +static const struct usb_endpoint_descriptor +fsl_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = USB_MAX_CTRL_PAYLOAD, +}; + +static void fsl_ep_fifo_flush(struct usb_ep *_ep); + +#ifdef CONFIG_PPC32 +#define fsl_readl(addr) in_le32(addr) +#define fsl_writel(val32, addr) out_le32(addr, val32) +#else +#define fsl_readl(addr) readl(addr) +#define fsl_writel(val32, addr) writel(val32, addr) +#endif + +/******************************************************************** + * Internal Used Function +********************************************************************/ +/*----------------------------------------------------------------- + * done() - retire a request; caller blocked irqs + * @status : request status to be set, only works when + * request is still in progress. + *--------------------------------------------------------------*/ +static void done(struct fsl_ep *ep, struct fsl_req *req, int status) +{ + struct fsl_udc *udc = NULL; + unsigned char stopped = ep->stopped; + struct ep_td_struct *curr_td, *next_td; + int j; + + udc = (struct fsl_udc *)ep->udc; + /* Removed the req from fsl_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; + + /* Free dtd for the request */ + next_td = req->head; + for (j = 0; j < req->dtd_count; j++) { + curr_td = next_td; + if (j != req->dtd_count - 1) { + next_td = curr_td->next_td_virt; + } + dma_pool_free(udc->td_pool, curr_td, curr_td->td_dma); + } + + if (req->mapped) { + dma_unmap_single(ep->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(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + + if (status && (status != -ESHUTDOWN)) + VDBG("complete %s req %p stat %d len %u/%u", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + ep->stopped = 1; + + spin_unlock(&ep->udc->lock); + /* complete() is from gadget layer, + * eg fsg->bulk_in_complete() */ + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + + spin_lock(&ep->udc->lock); + ep->stopped = stopped; +} + +/*----------------------------------------------------------------- + * nuke(): delete all requests related to this ep + * called with spinlock held + *--------------------------------------------------------------*/ +static void nuke(struct fsl_ep *ep, int status) +{ + ep->stopped = 1; + + /* Flush fifo */ + fsl_ep_fifo_flush(&ep->ep); + + /* Whether this eq has request linked */ + while (!list_empty(&ep->queue)) { + struct fsl_req *req = NULL; + + req = list_entry(ep->queue.next, struct fsl_req, queue); + done(ep, req, status); + } +} + +/*------------------------------------------------------------------ + Internal Hardware related function + ------------------------------------------------------------------*/ + +static int dr_controller_setup(struct fsl_udc *udc) +{ + unsigned int tmp, portctrl; +#ifndef CONFIG_ARCH_MXC + unsigned int ctrl; +#endif + unsigned long timeout; +#define FSL_UDC_RESET_TIMEOUT 1000 + + /* Config PHY interface */ + portctrl = fsl_readl(&dr_regs->portsc1); + portctrl &= ~(PORTSCX_PHY_TYPE_SEL | PORTSCX_PORT_WIDTH); + switch (udc->phy_mode) { + case FSL_USB2_PHY_ULPI: + portctrl |= PORTSCX_PTS_ULPI; + break; + case FSL_USB2_PHY_UTMI_WIDE: + portctrl |= PORTSCX_PTW_16BIT; + /* fall through */ + case FSL_USB2_PHY_UTMI: + portctrl |= PORTSCX_PTS_UTMI; + break; + case FSL_USB2_PHY_SERIAL: + portctrl |= PORTSCX_PTS_FSLS; + break; + default: + return -EINVAL; + } + fsl_writel(portctrl, &dr_regs->portsc1); + + /* Stop and reset the usb controller */ + tmp = fsl_readl(&dr_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + fsl_writel(tmp, &dr_regs->usbcmd); + + tmp = fsl_readl(&dr_regs->usbcmd); + tmp |= USB_CMD_CTRL_RESET; + fsl_writel(tmp, &dr_regs->usbcmd); + + /* Wait for reset to complete */ + timeout = jiffies + FSL_UDC_RESET_TIMEOUT; + while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) { + if (time_after(jiffies, timeout)) { + ERR("udc reset timeout!\n"); + return -ETIMEDOUT; + } + cpu_relax(); + } + + /* Set the controller as device mode */ + tmp = fsl_readl(&dr_regs->usbmode); + tmp |= USB_MODE_CTRL_MODE_DEVICE; + /* Disable Setup Lockout */ + tmp |= USB_MODE_SETUP_LOCK_OFF; + fsl_writel(tmp, &dr_regs->usbmode); + + /* Clear the setup status */ + fsl_writel(0, &dr_regs->usbsts); + + tmp = udc->ep_qh_dma; + tmp &= USB_EP_LIST_ADDRESS_MASK; + fsl_writel(tmp, &dr_regs->endpointlistaddr); + + VDBG("vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x", + udc->ep_qh, (int)tmp, + fsl_readl(&dr_regs->endpointlistaddr)); + + /* Config control enable i/o output, cpu endian register */ +#ifndef CONFIG_ARCH_MXC + ctrl = __raw_readl(&usb_sys_regs->control); + ctrl |= USB_CTRL_IOENB; + __raw_writel(ctrl, &usb_sys_regs->control); +#endif + +#if defined(CONFIG_PPC32) && !defined(CONFIG_NOT_COHERENT_CACHE) + /* Turn on cache snooping hardware, since some PowerPC platforms + * wholly rely on hardware to deal with cache coherent. */ + + /* Setup Snooping for all the 4GB space */ + tmp = SNOOP_SIZE_2GB; /* starts from 0x0, size 2G */ + __raw_writel(tmp, &usb_sys_regs->snoop1); + tmp |= 0x80000000; /* starts from 0x8000000, size 2G */ + __raw_writel(tmp, &usb_sys_regs->snoop2); +#endif + + return 0; +} + +/* Enable DR irq and set controller to run state */ +static void dr_controller_run(struct fsl_udc *udc) +{ + u32 temp; + + /* Enable DR irq reg */ + temp = USB_INTR_INT_EN | USB_INTR_ERR_INT_EN + | USB_INTR_PTC_DETECT_EN | USB_INTR_RESET_EN + | USB_INTR_DEVICE_SUSPEND | USB_INTR_SYS_ERR_EN; + + fsl_writel(temp, &dr_regs->usbintr); + + /* Clear stopped bit */ + udc->stopped = 0; + + /* Set the controller as device mode */ + temp = fsl_readl(&dr_regs->usbmode); + temp |= USB_MODE_CTRL_MODE_DEVICE; + fsl_writel(temp, &dr_regs->usbmode); + + /* Set controller to Run */ + temp = fsl_readl(&dr_regs->usbcmd); + temp |= USB_CMD_RUN_STOP; + fsl_writel(temp, &dr_regs->usbcmd); + + return; +} + +static void dr_controller_stop(struct fsl_udc *udc) +{ + unsigned int tmp; + + /* disable all INTR */ + fsl_writel(0, &dr_regs->usbintr); + + /* Set stopped bit for isr */ + udc->stopped = 1; + + /* disable IO output */ +/* usb_sys_regs->control = 0; */ + + /* set controller to Stop */ + tmp = fsl_readl(&dr_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + fsl_writel(tmp, &dr_regs->usbcmd); + + return; +} + +static void dr_ep_setup(unsigned char ep_num, unsigned char dir, + unsigned char ep_type) +{ + unsigned int tmp_epctrl = 0; + + tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (dir) { + if (ep_num) + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_TX_ENABLE; + tmp_epctrl |= ((unsigned int)(ep_type) + << EPCTRL_TX_EP_TYPE_SHIFT); + } else { + if (ep_num) + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_RX_ENABLE; + tmp_epctrl |= ((unsigned int)(ep_type) + << EPCTRL_RX_EP_TYPE_SHIFT); + } + + fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]); +} + +static void +dr_ep_change_stall(unsigned char ep_num, unsigned char dir, int value) +{ + u32 tmp_epctrl = 0; + + tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + + if (value) { + /* set the stall bit */ + if (dir) + tmp_epctrl |= EPCTRL_TX_EP_STALL; + else + tmp_epctrl |= EPCTRL_RX_EP_STALL; + } else { + /* clear the stall bit and reset data toggle */ + if (dir) { + tmp_epctrl &= ~EPCTRL_TX_EP_STALL; + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + } else { + tmp_epctrl &= ~EPCTRL_RX_EP_STALL; + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + } + } + fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]); +} + +/* Get stall status of a specific ep + Return: 0: not stalled; 1:stalled */ +static int dr_ep_get_stall(unsigned char ep_num, unsigned char dir) +{ + u32 epctrl; + + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (dir) + return (epctrl & EPCTRL_TX_EP_STALL) ? 1 : 0; + else + return (epctrl & EPCTRL_RX_EP_STALL) ? 1 : 0; +} + +/******************************************************************** + Internal Structure Build up functions +********************************************************************/ + +/*------------------------------------------------------------------ +* struct_ep_qh_setup(): set the Endpoint Capabilites field of QH + * @zlt: Zero Length Termination Select (1: disable; 0: enable) + * @mult: Mult field + ------------------------------------------------------------------*/ +static void struct_ep_qh_setup(struct fsl_udc *udc, unsigned char ep_num, + unsigned char dir, unsigned char ep_type, + unsigned int max_pkt_len, + unsigned int zlt, unsigned char mult) +{ + struct ep_queue_head *p_QH = &udc->ep_qh[2 * ep_num + dir]; + unsigned int tmp = 0; + + /* set the Endpoint Capabilites in QH */ + switch (ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + /* Interrupt On Setup (IOS). for control ep */ + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) + | EP_QUEUE_HEAD_IOS; + break; + case USB_ENDPOINT_XFER_ISOC: + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) + | (mult << EP_QUEUE_HEAD_MULT_POS); + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; + break; + default: + VDBG("error ep type is %d", ep_type); + return; + } + if (zlt) + tmp |= EP_QUEUE_HEAD_ZLT_SEL; + + p_QH->max_pkt_length = cpu_to_le32(tmp); + p_QH->next_dtd_ptr = 1; + p_QH->size_ioc_int_sts = 0; + + return; +} + +/* Setup qh structure and ep register for ep0. */ +static void ep0_setup(struct fsl_udc *udc) +{ + /* the intialization of an ep includes: fields in QH, Regs, + * fsl_ep struct */ + struct_ep_qh_setup(udc, 0, USB_RECV, USB_ENDPOINT_XFER_CONTROL, + USB_MAX_CTRL_PAYLOAD, 0, 0); + struct_ep_qh_setup(udc, 0, USB_SEND, USB_ENDPOINT_XFER_CONTROL, + USB_MAX_CTRL_PAYLOAD, 0, 0); + dr_ep_setup(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL); + dr_ep_setup(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL); + + return; + +} + +/*********************************************************************** + Endpoint Management Functions +***********************************************************************/ + +/*------------------------------------------------------------------------- + * when configurations are set, or when interface settings change + * for example the do_set_interface() in gadget layer, + * the driver will enable or disable the relevant endpoints + * ep0 doesn't use this routine. It is always enabled. +-------------------------------------------------------------------------*/ +static int fsl_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct fsl_udc *udc = NULL; + struct fsl_ep *ep = NULL; + unsigned short max = 0; + unsigned char mult = 0, zlt; + int retval = -EINVAL; + unsigned long flags = 0; + + ep = container_of(_ep, struct fsl_ep, ep); + + /* catch various bogus parameters */ + if (!_ep || !desc || ep->desc + || (desc->bDescriptorType != USB_DT_ENDPOINT)) + return -EINVAL; + + udc = ep->udc; + + if (!udc->driver || (udc->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + max = le16_to_cpu(desc->wMaxPacketSize); + + /* Disable automatic zlp generation. Driver is reponsible to indicate + * explicitly through req->req.zero. This is needed to enable multi-td + * request. */ + zlt = 1; + + /* Assume the max packet size from gadget is always correct */ + switch (desc->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + /* mult = 0. Execute N Transactions as demonstrated by + * the USB variable length packet protocol where N is + * computed using the Maximum Packet Length (dQH) and + * the Total Bytes field (dTD) */ + mult = 0; + break; + case USB_ENDPOINT_XFER_ISOC: + /* Calculate transactions needed for high bandwidth iso */ + mult = (unsigned char)(1 + ((max >> 11) & 0x03)); + max = max & 0x8ff; /* bit 0~10 */ + /* 3 transactions at most */ + if (mult > 3) + goto en_done; + break; + default: + goto en_done; + } + + spin_lock_irqsave(&udc->lock, flags); + ep->ep.maxpacket = max; + ep->desc = desc; + ep->stopped = 0; + + /* Controller related setup */ + /* Init EPx Queue Head (Ep Capabilites field in QH + * according to max, zlt, mult) */ + struct_ep_qh_setup(udc, (unsigned char) ep_index(ep), + (unsigned char) ((desc->bEndpointAddress & USB_DIR_IN) + ? USB_SEND : USB_RECV), + (unsigned char) (desc->bmAttributes + & USB_ENDPOINT_XFERTYPE_MASK), + max, zlt, mult); + + /* Init endpoint ctrl register */ + dr_ep_setup((unsigned char) ep_index(ep), + (unsigned char) ((desc->bEndpointAddress & USB_DIR_IN) + ? USB_SEND : USB_RECV), + (unsigned char) (desc->bmAttributes + & USB_ENDPOINT_XFERTYPE_MASK)); + + spin_unlock_irqrestore(&udc->lock, flags); + retval = 0; + + VDBG("enabled %s (ep%d%s) maxpacket %d",ep->ep.name, + ep->desc->bEndpointAddress & 0x0f, + (desc->bEndpointAddress & USB_DIR_IN) + ? "in" : "out", max); +en_done: + return retval; +} + +/*--------------------------------------------------------------------- + * @ep : the ep being unconfigured. May not be ep0 + * Any pending and uncomplete req will complete with status (-ESHUTDOWN) +*---------------------------------------------------------------------*/ +static int fsl_ep_disable(struct usb_ep *_ep) +{ + struct fsl_udc *udc = NULL; + struct fsl_ep *ep = NULL; + unsigned long flags = 0; + u32 epctrl; + int ep_num; + + ep = container_of(_ep, struct fsl_ep, ep); + if (!_ep || !ep->desc) { + VDBG("%s not enabled", _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + /* disable ep on controller */ + ep_num = ep_index(ep); + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) + epctrl &= ~EPCTRL_TX_ENABLE; + else + epctrl &= ~EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + + udc = (struct fsl_udc *)ep->udc; + spin_lock_irqsave(&udc->lock, flags); + + /* nuke all pending requests (does flush) */ + nuke(ep, -ESHUTDOWN); + + ep->desc = NULL; + ep->stopped = 1; + spin_unlock_irqrestore(&udc->lock, flags); + + VDBG("disabled %s OK", _ep->name); + return 0; +} + +/*--------------------------------------------------------------------- + * allocate a request object used by this endpoint + * the main operation is to insert the req->queue to the eq->queue + * Returns the request, or null if one could not be allocated +*---------------------------------------------------------------------*/ +static struct usb_request * +fsl_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct fsl_req *req = NULL; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + req->req.dma = DMA_ADDR_INVALID; + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void fsl_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fsl_req *req = NULL; + + req = container_of(_req, struct fsl_req, req); + + if (_req) + kfree(req); +} + +/*-------------------------------------------------------------------------*/ +static void fsl_queue_td(struct fsl_ep *ep, struct fsl_req *req) +{ + int i = ep_index(ep) * 2 + ep_is_in(ep); + u32 temp, bitmask, tmp_stat; + struct ep_queue_head *dQH = &ep->udc->ep_qh[i]; + + /* VDBG("QH addr Register 0x%8x", dr_regs->endpointlistaddr); + VDBG("ep_qh[%d] addr is 0x%8x", i, (u32)&(ep->udc->ep_qh[i])); */ + + bitmask = ep_is_in(ep) + ? (1 << (ep_index(ep) + 16)) + : (1 << (ep_index(ep))); + + /* check if the pipe is empty */ + if (!(list_empty(&ep->queue))) { + /* Add td to the end */ + struct fsl_req *lastreq; + lastreq = list_entry(ep->queue.prev, struct fsl_req, queue); + lastreq->tail->next_td_ptr = + cpu_to_le32(req->head->td_dma & DTD_ADDR_MASK); + /* Read prime bit, if 1 goto done */ + if (fsl_readl(&dr_regs->endpointprime) & bitmask) + goto out; + + do { + /* Set ATDTW bit in USBCMD */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp | USB_CMD_ATDTW, &dr_regs->usbcmd); + + /* Read correct status bit */ + tmp_stat = fsl_readl(&dr_regs->endptstatus) & bitmask; + + } while (!(fsl_readl(&dr_regs->usbcmd) & USB_CMD_ATDTW)); + + /* Write ATDTW bit to 0 */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp & ~USB_CMD_ATDTW, &dr_regs->usbcmd); + + if (tmp_stat) + goto out; + } + + /* Write dQH next pointer and terminate bit to 0 */ + temp = req->head->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK; + dQH->next_dtd_ptr = cpu_to_le32(temp); + + /* Clear active and halt bit */ + temp = cpu_to_le32(~(EP_QUEUE_HEAD_STATUS_ACTIVE + | EP_QUEUE_HEAD_STATUS_HALT)); + dQH->size_ioc_int_sts &= temp; + + /* Ensure that updates to the QH will occure before priming. */ + wmb(); + + /* Prime endpoint by writing 1 to ENDPTPRIME */ + temp = ep_is_in(ep) + ? (1 << (ep_index(ep) + 16)) + : (1 << (ep_index(ep))); + fsl_writel(temp, &dr_regs->endpointprime); +out: + return; +} + +/* Fill in the dTD structure + * @req: request that the transfer belongs to + * @length: return actually data length of the dTD + * @dma: return dma address of the dTD + * @is_last: return flag if it is the last dTD of the request + * return: pointer to the built dTD */ +static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, + dma_addr_t *dma, int *is_last) +{ + u32 swap_temp; + struct ep_td_struct *dtd; + + /* how big will this transfer be? */ + *length = min(req->req.length - req->req.actual, + (unsigned)EP_MAX_LENGTH_TRANSFER); + + dtd = dma_pool_alloc(udc_controller->td_pool, GFP_KERNEL, dma); + if (dtd == NULL) + return dtd; + + dtd->td_dma = *dma; + /* Clear reserved field */ + swap_temp = cpu_to_le32(dtd->size_ioc_sts); + swap_temp &= ~DTD_RESERVED_FIELDS; + dtd->size_ioc_sts = cpu_to_le32(swap_temp); + + /* Init all of buffer page pointers */ + swap_temp = (u32) (req->req.dma + req->req.actual); + dtd->buff_ptr0 = cpu_to_le32(swap_temp); + dtd->buff_ptr1 = cpu_to_le32(swap_temp + 0x1000); + dtd->buff_ptr2 = cpu_to_le32(swap_temp + 0x2000); + dtd->buff_ptr3 = cpu_to_le32(swap_temp + 0x3000); + dtd->buff_ptr4 = cpu_to_le32(swap_temp + 0x4000); + + req->req.actual += *length; + + /* zlp is needed if req->req.zero is set */ + if (req->req.zero) { + if (*length == 0 || (*length % req->ep->ep.maxpacket) != 0) + *is_last = 1; + else + *is_last = 0; + } else if (req->req.length == req->req.actual) + *is_last = 1; + else + *is_last = 0; + + if ((*is_last) == 0) + VDBG("multi-dtd request!"); + /* Fill in the transfer size; set active bit */ + swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE); + + /* Enable interrupt for the last dtd of a request */ + if (*is_last && !req->req.no_interrupt) + swap_temp |= DTD_IOC; + + dtd->size_ioc_sts = cpu_to_le32(swap_temp); + + mb(); + + VDBG("length = %d address= 0x%x", *length, (int)*dma); + + return dtd; +} + +/* Generate dtd chain for a request */ +static int fsl_req_to_dtd(struct fsl_req *req) +{ + unsigned count; + int is_last; + int is_first =1; + struct ep_td_struct *last_dtd = NULL, *dtd; + dma_addr_t dma; + + do { + dtd = fsl_build_dtd(req, &count, &dma, &is_last); + if (dtd == NULL) + return -ENOMEM; + + if (is_first) { + is_first = 0; + req->head = dtd; + } else { + last_dtd->next_td_ptr = cpu_to_le32(dma); + last_dtd->next_td_virt = dtd; + } + last_dtd = dtd; + + req->dtd_count++; + } while (!is_last); + + dtd->next_td_ptr = cpu_to_le32(DTD_NEXT_TERMINATE); + + req->tail = dtd; + + return 0; +} + +/* queues (submits) an I/O request to an endpoint */ +static int +fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); + struct fsl_req *req = container_of(_req, struct fsl_req, req); + struct fsl_udc *udc; + unsigned long flags; + int is_iso = 0; + + /* catch various bogus parameters */ + if (!_req || !req->req.complete || !req->req.buf + || !list_empty(&req->queue)) { + VDBG("%s, bad params", __func__); + return -EINVAL; + } + if (unlikely(!_ep || !ep->desc)) { + VDBG("%s, bad ep", __func__); + return -EINVAL; + } + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (req->req.length > ep->ep.maxpacket) + return -EMSGSIZE; + is_iso = 1; + } + + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + req->ep = ep; + + /* map virtual address to hardware */ + if (req->req.dma == DMA_ADDR_INVALID) { + req->req.dma = dma_map_single(ep->udc->gadget.dev.parent, + req->req.buf, + req->req.length, ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + req->mapped = 1; + } else { + dma_sync_single_for_device(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + req->mapped = 0; + } + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->dtd_count = 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* build dtds and push them to device queue */ + if (!fsl_req_to_dtd(req)) { + fsl_queue_td(ep, req); + } else { + spin_unlock_irqrestore(&udc->lock, flags); + return -ENOMEM; + } + + /* Update ep0 state */ + if ((ep_index(ep) == 0)) + udc->ep0_state = DATA_STATE_XMIT; + + /* irq handler advances the queue */ + if (req != NULL) + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/* dequeues (cancels, unlinks) an I/O request from an endpoint */ +static int fsl_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); + struct fsl_req *req; + unsigned long flags; + int ep_num, stopped, ret = 0; + u32 epctrl; + + if (!_ep || !_req) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + stopped = ep->stopped; + + /* Stop the ep before we deal with the queue */ + ep->stopped = 1; + ep_num = ep_index(ep); + epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) + epctrl &= ~EPCTRL_TX_ENABLE; + else + epctrl &= ~EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + + /* 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) { + ret = -EINVAL; + goto out; + } + + /* The request is in progress, or completed but not dequeued */ + if (ep->queue.next == &req->queue) { + _req->status = -ECONNRESET; + fsl_ep_fifo_flush(_ep); /* flush current transfer */ + + /* The request isn't the last request in this ep queue */ + if (req->queue.next != &ep->queue) { + struct ep_queue_head *qh; + struct fsl_req *next_req; + + qh = ep->qh; + next_req = list_entry(req->queue.next, struct fsl_req, + queue); + + /* Point the QH to the first TD of next request */ + fsl_writel((u32) next_req->head, &qh->curr_dtd_ptr); + } + + /* The request hasn't been processed, patch up the TD chain */ + } else { + struct fsl_req *prev_req; + + prev_req = list_entry(req->queue.prev, struct fsl_req, queue); + fsl_writel(fsl_readl(&req->tail->next_td_ptr), + &prev_req->tail->next_td_ptr); + + } + + done(ep, req, -ECONNRESET); + + /* Enable EP */ +out: epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); + if (ep_is_in(ep)) + epctrl |= EPCTRL_TX_ENABLE; + else + epctrl |= EPCTRL_RX_ENABLE; + fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); + ep->stopped = stopped; + + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +/*-------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------- + * modify the endpoint halt feature + * @ep: the non-isochronous endpoint being stalled + * @value: 1--set halt 0--clear halt + * Returns zero, or a negative error code. +*----------------------------------------------------------------*/ +static int fsl_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct fsl_ep *ep = NULL; + unsigned long flags = 0; + int status = -EOPNOTSUPP; /* operation not supported */ + unsigned char ep_dir = 0, ep_num = 0; + struct fsl_udc *udc = NULL; + + ep = container_of(_ep, struct fsl_ep, ep); + udc = ep->udc; + if (!_ep || !ep->desc) { + status = -EINVAL; + goto out; + } + + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + status = -EOPNOTSUPP; + goto out; + } + + /* Attempt to halt IN ep will fail if any transfer requests + * are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + status = -EAGAIN; + goto out; + } + + status = 0; + ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; + ep_num = (unsigned char)(ep_index(ep)); + spin_lock_irqsave(&ep->udc->lock, flags); + dr_ep_change_stall(ep_num, ep_dir, value); + spin_unlock_irqrestore(&ep->udc->lock, flags); + + if (ep_index(ep) == 0) { + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = 0; + } +out: + VDBG(" %s %s halt stat %d", ep->ep.name, + value ? "set" : "clear", status); + + return status; +} + +static void fsl_ep_fifo_flush(struct usb_ep *_ep) +{ + struct fsl_ep *ep; + int ep_num, ep_dir; + u32 bits; + unsigned long timeout; +#define FSL_UDC_FLUSH_TIMEOUT 1000 + + if (!_ep) { + return; + } else { + ep = container_of(_ep, struct fsl_ep, ep); + if (!ep->desc) + return; + } + ep_num = ep_index(ep); + ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; + + if (ep_num == 0) + bits = (1 << 16) | 1; + else if (ep_dir == USB_SEND) + bits = 1 << (16 + ep_num); + else + bits = 1 << ep_num; + + timeout = jiffies + FSL_UDC_FLUSH_TIMEOUT; + do { + fsl_writel(bits, &dr_regs->endptflush); + + /* Wait until flush complete */ + while (fsl_readl(&dr_regs->endptflush)) { + if (time_after(jiffies, timeout)) { + ERR("ep flush timeout\n"); + return; + } + cpu_relax(); + } + /* See if we need to flush again */ + } while (fsl_readl(&dr_regs->endptstatus) & bits); +} + +static struct usb_ep_ops fsl_ep_ops = { + .enable = fsl_ep_enable, + .disable = fsl_ep_disable, + + .alloc_request = fsl_alloc_request, + .free_request = fsl_free_request, + + .queue = fsl_ep_queue, + .dequeue = fsl_ep_dequeue, + + .set_halt = fsl_ep_set_halt, + .fifo_flush = fsl_ep_fifo_flush, /* flush fifo */ +}; + +/*------------------------------------------------------------------------- + Gadget Driver Layer Operations +-------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------- + * Get the current frame number (from DR frame_index Reg ) + *----------------------------------------------------------------------*/ +static int fsl_get_frame(struct usb_gadget *gadget) +{ + return (int)(fsl_readl(&dr_regs->frindex) & USB_FRINDEX_MASKS); +} + +/*----------------------------------------------------------------------- + * Tries to wake up the host connected to this gadget + -----------------------------------------------------------------------*/ +static int fsl_wakeup(struct usb_gadget *gadget) +{ + struct fsl_udc *udc = container_of(gadget, struct fsl_udc, gadget); + u32 portsc; + + /* Remote wakeup feature not enabled by host */ + if (!udc->remote_wakeup) + return -ENOTSUPP; + + portsc = fsl_readl(&dr_regs->portsc1); + /* not suspended? */ + if (!(portsc & PORTSCX_PORT_SUSPEND)) + return 0; + /* trigger force resume */ + portsc |= PORTSCX_PORT_FORCE_RESUME; + fsl_writel(portsc, &dr_regs->portsc1); + return 0; +} + +static int can_pullup(struct fsl_udc *udc) +{ + return udc->driver && udc->softconnect && udc->vbus_active; +} + +/* Notify controller that VBUS is powered, Called by whatever + detects VBUS sessions */ +static int fsl_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct fsl_udc *udc; + unsigned long flags; + + udc = container_of(gadget, struct fsl_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + VDBG("VBUS %s", is_active ? "on" : "off"); + udc->vbus_active = (is_active != 0); + if (can_pullup(udc)) + fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + else + fsl_writel((fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/* constrain controller's VBUS power usage + * This call is used by gadget drivers during SET_CONFIGURATION calls, + * reporting how much power the device may consume. For example, this + * could affect how quickly batteries are recharged. + * + * Returns zero on success, else negative errno. + */ +static int fsl_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct fsl_udc *udc; + + udc = container_of(gadget, struct fsl_udc, gadget); + if (udc->transceiver) + return otg_set_power(udc->transceiver, mA); + return -ENOTSUPP; +} + +/* Change Data+ pullup status + * this func is used by usb_gadget_connect/disconnet + */ +static int fsl_pullup(struct usb_gadget *gadget, int is_on) +{ + struct fsl_udc *udc; + + udc = container_of(gadget, struct fsl_udc, gadget); + udc->softconnect = (is_on != 0); + if (can_pullup(udc)) + fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + else + fsl_writel((fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP), + &dr_regs->usbcmd); + + return 0; +} + +/* defined in gadget.h */ +static struct usb_gadget_ops fsl_gadget_ops = { + .get_frame = fsl_get_frame, + .wakeup = fsl_wakeup, +/* .set_selfpowered = fsl_set_selfpowered, */ /* Always selfpowered */ + .vbus_session = fsl_vbus_session, + .vbus_draw = fsl_vbus_draw, + .pullup = fsl_pullup, +}; + +/* Set protocol stall on ep0, protocol stall will automatically be cleared + on new transaction */ +static void ep0stall(struct fsl_udc *udc) +{ + u32 tmp; + + /* must set tx and rx to stall at the same time */ + tmp = fsl_readl(&dr_regs->endptctrl[0]); + tmp |= EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL; + fsl_writel(tmp, &dr_regs->endptctrl[0]); + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = 0; +} + +/* Prime a status phase for ep0 */ +static int ep0_prime_status(struct fsl_udc *udc, int direction) +{ + struct fsl_req *req = udc->status_req; + struct fsl_ep *ep; + + if (direction == EP_DIR_IN) + udc->ep0_dir = USB_DIR_IN; + else + udc->ep0_dir = USB_DIR_OUT; + + ep = &udc->eps[0]; + udc->ep0_state = WAIT_FOR_OUT_STATUS; + + req->ep = ep; + req->req.length = 0; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->req.complete = NULL; + req->dtd_count = 0; + + if (fsl_req_to_dtd(req) == 0) + fsl_queue_td(ep, req); + else + return -ENOMEM; + + list_add_tail(&req->queue, &ep->queue); + + return 0; +} + +static void udc_reset_ep_queue(struct fsl_udc *udc, u8 pipe) +{ + struct fsl_ep *ep = get_ep_by_pipe(udc, pipe); + + if (ep->name) + nuke(ep, -ESHUTDOWN); +} + +/* + * ch9 Set address + */ +static void ch9setaddress(struct fsl_udc *udc, u16 value, u16 index, u16 length) +{ + /* Save the new address to device struct */ + udc->device_address = (u8) value; + /* Update usb state */ + udc->usb_state = USB_STATE_ADDRESS; + /* Status phase */ + if (ep0_prime_status(udc, EP_DIR_IN)) + ep0stall(udc); +} + +/* + * ch9 Get status + */ +static void ch9getstatus(struct fsl_udc *udc, u8 request_type, u16 value, + u16 index, u16 length) +{ + u16 tmp = 0; /* Status, cpu endian */ + struct fsl_req *req; + struct fsl_ep *ep; + + ep = &udc->eps[0]; + + if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + /* Get device status */ + tmp = 1 << USB_DEVICE_SELF_POWERED; + tmp |= udc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) { + /* Get interface status */ + /* We don't have interface information in udc driver */ + tmp = 0; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_ENDPOINT) { + /* Get endpoint status */ + struct fsl_ep *target_ep; + + target_ep = get_ep_by_pipe(udc, get_pipe_by_windex(index)); + + /* stall if endpoint doesn't exist */ + if (!target_ep->desc) + goto stall; + tmp = dr_ep_get_stall(ep_index(target_ep), ep_is_in(target_ep)) + << USB_ENDPOINT_HALT; + } + + udc->ep0_dir = USB_DIR_IN; + /* Borrow the per device status_req */ + req = udc->status_req; + /* Fill in the reqest structure */ + *((u16 *) req->req.buf) = cpu_to_le16(tmp); + req->ep = ep; + req->req.length = 2; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->req.complete = NULL; + req->dtd_count = 0; + + /* prime the data phase */ + if ((fsl_req_to_dtd(req) == 0)) + fsl_queue_td(ep, req); + else /* no mem */ + goto stall; + + list_add_tail(&req->queue, &ep->queue); + udc->ep0_state = DATA_STATE_XMIT; + return; +stall: + ep0stall(udc); +} + +static void setup_received_irq(struct fsl_udc *udc, + struct usb_ctrlrequest *setup) +{ + u16 wValue = le16_to_cpu(setup->wValue); + u16 wIndex = le16_to_cpu(setup->wIndex); + u16 wLength = le16_to_cpu(setup->wLength); + + udc_reset_ep_queue(udc, 0); + + /* We process some stardard setup requests here */ + switch (setup->bRequest) { + case USB_REQ_GET_STATUS: + /* Data+Status phase from udc */ + if ((setup->bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) + != (USB_DIR_IN | USB_TYPE_STANDARD)) + break; + ch9getstatus(udc, setup->bRequestType, wValue, wIndex, wLength); + return; + + case USB_REQ_SET_ADDRESS: + /* Status phase from udc */ + if (setup->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD + | USB_RECIP_DEVICE)) + break; + ch9setaddress(udc, wValue, wIndex, wLength); + return; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* Status phase from udc */ + { + int rc = -EOPNOTSUPP; + + if ((setup->bRequestType & (USB_RECIP_MASK | USB_TYPE_MASK)) + == (USB_RECIP_ENDPOINT | USB_TYPE_STANDARD)) { + int pipe = get_pipe_by_windex(wIndex); + struct fsl_ep *ep; + + if (wValue != 0 || wLength != 0 || pipe > udc->max_ep) + break; + ep = get_ep_by_pipe(udc, pipe); + + spin_unlock(&udc->lock); + rc = fsl_ep_set_halt(&ep->ep, + (setup->bRequest == USB_REQ_SET_FEATURE) + ? 1 : 0); + spin_lock(&udc->lock); + + } else if ((setup->bRequestType & (USB_RECIP_MASK + | USB_TYPE_MASK)) == (USB_RECIP_DEVICE + | USB_TYPE_STANDARD)) { + /* Note: The driver has not include OTG support yet. + * This will be set when OTG support is added */ + if (!gadget_is_otg(&udc->gadget)) + break; + else if (setup->bRequest == USB_DEVICE_B_HNP_ENABLE) + udc->gadget.b_hnp_enable = 1; + else if (setup->bRequest == USB_DEVICE_A_HNP_SUPPORT) + udc->gadget.a_hnp_support = 1; + else if (setup->bRequest == + USB_DEVICE_A_ALT_HNP_SUPPORT) + udc->gadget.a_alt_hnp_support = 1; + else + break; + rc = 0; + } else + break; + + if (rc == 0) { + if (ep0_prime_status(udc, EP_DIR_IN)) + ep0stall(udc); + } + return; + } + + default: + break; + } + + /* Requests handled by gadget */ + if (wLength) { + /* Data phase from gadget, status phase from udc */ + udc->ep0_dir = (setup->bRequestType & USB_DIR_IN) + ? USB_DIR_IN : USB_DIR_OUT; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) + ep0stall(udc); + spin_lock(&udc->lock); + udc->ep0_state = (setup->bRequestType & USB_DIR_IN) + ? DATA_STATE_XMIT : DATA_STATE_RECV; + } else { + /* No data phase, IN status from gadget */ + udc->ep0_dir = USB_DIR_IN; + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) < 0) + ep0stall(udc); + spin_lock(&udc->lock); + udc->ep0_state = WAIT_FOR_OUT_STATUS; + } +} + +/* Process request for Data or Status phase of ep0 + * prime status phase if needed */ +static void ep0_req_complete(struct fsl_udc *udc, struct fsl_ep *ep0, + struct fsl_req *req) +{ + if (udc->usb_state == USB_STATE_ADDRESS) { + /* Set the new address */ + u32 new_address = (u32) udc->device_address; + fsl_writel(new_address << USB_DEVICE_ADDRESS_BIT_POS, + &dr_regs->deviceaddr); + } + + done(ep0, req, 0); + + switch (udc->ep0_state) { + case DATA_STATE_XMIT: + /* receive status phase */ + if (ep0_prime_status(udc, EP_DIR_OUT)) + ep0stall(udc); + break; + case DATA_STATE_RECV: + /* send status phase */ + if (ep0_prime_status(udc, EP_DIR_IN)) + ep0stall(udc); + break; + case WAIT_FOR_OUT_STATUS: + udc->ep0_state = WAIT_FOR_SETUP; + break; + case WAIT_FOR_SETUP: + ERR("Unexpect ep0 packets\n"); + break; + default: + ep0stall(udc); + break; + } +} + +/* Tripwire mechanism to ensure a setup packet payload is extracted without + * being corrupted by another incoming setup packet */ +static void tripwire_handler(struct fsl_udc *udc, u8 ep_num, u8 *buffer_ptr) +{ + u32 temp; + struct ep_queue_head *qh; + + qh = &udc->ep_qh[ep_num * 2 + EP_DIR_OUT]; + + /* Clear bit in ENDPTSETUPSTAT */ + temp = fsl_readl(&dr_regs->endptsetupstat); + fsl_writel(temp | (1 << ep_num), &dr_regs->endptsetupstat); + + /* while a hazard exists when setup package arrives */ + do { + /* Set Setup Tripwire */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp | USB_CMD_SUTW, &dr_regs->usbcmd); + + /* Copy the setup packet to local buffer */ + memcpy(buffer_ptr, (u8 *) qh->setup_buffer, 8); + } while (!(fsl_readl(&dr_regs->usbcmd) & USB_CMD_SUTW)); + + /* Clear Setup Tripwire */ + temp = fsl_readl(&dr_regs->usbcmd); + fsl_writel(temp & ~USB_CMD_SUTW, &dr_regs->usbcmd); +} + +/* process-ep_req(): free the completed Tds for this req */ +static int process_ep_req(struct fsl_udc *udc, int pipe, + struct fsl_req *curr_req) +{ + struct ep_td_struct *curr_td; + int td_complete, actual, remaining_length, j, tmp; + int status = 0; + int errors = 0; + struct ep_queue_head *curr_qh = &udc->ep_qh[pipe]; + int direction = pipe % 2; + + curr_td = curr_req->head; + td_complete = 0; + actual = curr_req->req.length; + + for (j = 0; j < curr_req->dtd_count; j++) { + remaining_length = (le32_to_cpu(curr_td->size_ioc_sts) + & DTD_PACKET_SIZE) + >> DTD_LENGTH_BIT_POS; + actual -= remaining_length; + + if ((errors = le32_to_cpu(curr_td->size_ioc_sts) & + DTD_ERROR_MASK)) { + if (errors & DTD_STATUS_HALTED) { + ERR("dTD error %08x QH=%d\n", errors, pipe); + /* Clear the errors and Halt condition */ + tmp = le32_to_cpu(curr_qh->size_ioc_int_sts); + tmp &= ~errors; + curr_qh->size_ioc_int_sts = cpu_to_le32(tmp); + status = -EPIPE; + /* FIXME: continue with next queued TD? */ + + break; + } + if (errors & DTD_STATUS_DATA_BUFF_ERR) { + VDBG("Transfer overflow"); + status = -EPROTO; + break; + } else if (errors & DTD_STATUS_TRANSACTION_ERR) { + VDBG("ISO error"); + status = -EILSEQ; + break; + } else + ERR("Unknown error has occured (0x%x)!\n", + errors); + + } else if (le32_to_cpu(curr_td->size_ioc_sts) + & DTD_STATUS_ACTIVE) { + VDBG("Request not complete"); + status = REQ_UNCOMPLETE; + return status; + } else if (remaining_length) { + if (direction) { + VDBG("Transmit dTD remaining length not zero"); + status = -EPROTO; + break; + } else { + td_complete++; + break; + } + } else { + td_complete++; + VDBG("dTD transmitted successful"); + } + + if (j != curr_req->dtd_count - 1) + curr_td = (struct ep_td_struct *)curr_td->next_td_virt; + } + + if (status) + return status; + + curr_req->req.actual = actual; + + return 0; +} + +/* Process a DTD completion interrupt */ +static void dtd_complete_irq(struct fsl_udc *udc) +{ + u32 bit_pos; + int i, ep_num, direction, bit_mask, status; + struct fsl_ep *curr_ep; + struct fsl_req *curr_req, *temp_req; + + /* Clear the bits in the register */ + bit_pos = fsl_readl(&dr_regs->endptcomplete); + fsl_writel(bit_pos, &dr_regs->endptcomplete); + + if (!bit_pos) + return; + + for (i = 0; i < udc->max_ep * 2; i++) { + ep_num = i >> 1; + direction = i % 2; + + bit_mask = 1 << (ep_num + 16 * direction); + + if (!(bit_pos & bit_mask)) + continue; + + curr_ep = get_ep_by_pipe(udc, i); + + /* If the ep is configured */ + if (curr_ep->name == NULL) { + WARNING("Invalid EP?"); + continue; + } + + /* process the req queue until an uncomplete request */ + list_for_each_entry_safe(curr_req, temp_req, &curr_ep->queue, + queue) { + status = process_ep_req(udc, i, curr_req); + + VDBG("status of process_ep_req= %d, ep = %d", + status, ep_num); + if (status == REQ_UNCOMPLETE) + break; + /* write back status to req */ + curr_req->req.status = status; + + if (ep_num == 0) { + ep0_req_complete(udc, curr_ep, curr_req); + break; + } else + done(curr_ep, curr_req, status); + } + } +} + +/* Process a port change interrupt */ +static void port_change_irq(struct fsl_udc *udc) +{ + u32 speed; + + /* Bus resetting is finished */ + if (!(fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET)) { + /* Get the speed */ + speed = (fsl_readl(&dr_regs->portsc1) + & PORTSCX_PORT_SPEED_MASK); + switch (speed) { + case PORTSCX_PORT_SPEED_HIGH: + udc->gadget.speed = USB_SPEED_HIGH; + break; + case PORTSCX_PORT_SPEED_FULL: + udc->gadget.speed = USB_SPEED_FULL; + break; + case PORTSCX_PORT_SPEED_LOW: + udc->gadget.speed = USB_SPEED_LOW; + break; + default: + udc->gadget.speed = USB_SPEED_UNKNOWN; + break; + } + } + + /* Update USB state */ + if (!udc->resume_state) + udc->usb_state = USB_STATE_DEFAULT; +} + +/* Process suspend interrupt */ +static void suspend_irq(struct fsl_udc *udc) +{ + udc->resume_state = udc->usb_state; + udc->usb_state = USB_STATE_SUSPENDED; + + /* report suspend to the driver, serial.c does not support this */ + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void bus_resume(struct fsl_udc *udc) +{ + udc->usb_state = udc->resume_state; + udc->resume_state = 0; + + /* report resume to the driver, serial.c does not support this */ + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +/* Clear up all ep queues */ +static int reset_queues(struct fsl_udc *udc) +{ + u8 pipe; + + for (pipe = 0; pipe < udc->max_pipes; pipe++) + udc_reset_ep_queue(udc, pipe); + + /* report disconnect; the driver is already quiesced */ + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + + return 0; +} + +/* Process reset interrupt */ +static void reset_irq(struct fsl_udc *udc) +{ + u32 temp; + unsigned long timeout; + + /* Clear the device address */ + temp = fsl_readl(&dr_regs->deviceaddr); + fsl_writel(temp & ~USB_DEVICE_ADDRESS_MASK, &dr_regs->deviceaddr); + + udc->device_address = 0; + + /* Clear usb state */ + udc->resume_state = 0; + udc->ep0_dir = 0; + udc->ep0_state = WAIT_FOR_SETUP; + udc->remote_wakeup = 0; /* default to 0 on reset */ + udc->gadget.b_hnp_enable = 0; + udc->gadget.a_hnp_support = 0; + udc->gadget.a_alt_hnp_support = 0; + + /* Clear all the setup token semaphores */ + temp = fsl_readl(&dr_regs->endptsetupstat); + fsl_writel(temp, &dr_regs->endptsetupstat); + + /* Clear all the endpoint complete status bits */ + temp = fsl_readl(&dr_regs->endptcomplete); + fsl_writel(temp, &dr_regs->endptcomplete); + + timeout = jiffies + 100; + while (fsl_readl(&dr_regs->endpointprime)) { + /* Wait until all endptprime bits cleared */ + if (time_after(jiffies, timeout)) { + ERR("Timeout for reset\n"); + break; + } + cpu_relax(); + } + + /* Write 1s to the flush register */ + fsl_writel(0xffffffff, &dr_regs->endptflush); + + if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) { + VDBG("Bus reset"); + /* Reset all the queues, include XD, dTD, EP queue + * head and TR Queue */ + reset_queues(udc); + udc->usb_state = USB_STATE_DEFAULT; + } else { + VDBG("Controller reset"); + /* initialize usb hw reg except for regs for EP, not + * touch usbintr reg */ + dr_controller_setup(udc); + + /* Reset all internal used Queues */ + reset_queues(udc); + + ep0_setup(udc); + + /* Enable DR IRQ reg, Set Run bit, change udc state */ + dr_controller_run(udc); + udc->usb_state = USB_STATE_ATTACHED; + } +} + +/* + * USB device controller interrupt handler + */ +static irqreturn_t fsl_udc_irq(int irq, void *_udc) +{ + struct fsl_udc *udc = _udc; + u32 irq_src; + irqreturn_t status = IRQ_NONE; + unsigned long flags; + + /* Disable ISR for OTG host mode */ + if (udc->stopped) + return IRQ_NONE; + spin_lock_irqsave(&udc->lock, flags); + irq_src = fsl_readl(&dr_regs->usbsts) & fsl_readl(&dr_regs->usbintr); + /* Clear notification bits */ + fsl_writel(irq_src, &dr_regs->usbsts); + + /* VDBG("irq_src [0x%8x]", irq_src); */ + + /* Need to resume? */ + if (udc->usb_state == USB_STATE_SUSPENDED) + if ((fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_SUSPEND) == 0) + bus_resume(udc); + + /* USB Interrupt */ + if (irq_src & USB_STS_INT) { + VDBG("Packet int"); + /* Setup package, we only support ep0 as control ep */ + if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) { + tripwire_handler(udc, 0, + (u8 *) (&udc->local_setup_buff)); + setup_received_irq(udc, &udc->local_setup_buff); + status = IRQ_HANDLED; + } + + /* completion of dtd */ + if (fsl_readl(&dr_regs->endptcomplete)) { + dtd_complete_irq(udc); + status = IRQ_HANDLED; + } + } + + /* SOF (for ISO transfer) */ + if (irq_src & USB_STS_SOF) { + status = IRQ_HANDLED; + } + + /* Port Change */ + if (irq_src & USB_STS_PORT_CHANGE) { + port_change_irq(udc); + status = IRQ_HANDLED; + } + + /* Reset Received */ + if (irq_src & USB_STS_RESET) { + reset_irq(udc); + status = IRQ_HANDLED; + } + + /* Sleep Enable (Suspend) */ + if (irq_src & USB_STS_SUSPEND) { + suspend_irq(udc); + status = IRQ_HANDLED; + } + + if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { + VDBG("Error IRQ %x", irq_src); + } + + spin_unlock_irqrestore(&udc->lock, flags); + return status; +} + +/*----------------------------------------------------------------* + * Hook to gadget drivers + * Called by initialization code of gadget drivers +*----------------------------------------------------------------*/ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + int retval = -ENODEV; + unsigned long flags = 0; + + if (!udc_controller) + return -ENODEV; + + if (!driver || (driver->speed != USB_SPEED_FULL + && driver->speed != USB_SPEED_HIGH) + || !driver->bind || !driver->disconnect + || !driver->setup) + return -EINVAL; + + if (udc_controller->driver) + return -EBUSY; + + /* lock is needed but whether should use this lock or another */ + spin_lock_irqsave(&udc_controller->lock, flags); + + driver->driver.bus = NULL; + /* hook up the driver */ + udc_controller->driver = driver; + udc_controller->gadget.dev.driver = &driver->driver; + spin_unlock_irqrestore(&udc_controller->lock, flags); + + /* bind udc driver to gadget driver */ + retval = driver->bind(&udc_controller->gadget); + if (retval) { + VDBG("bind to %s --> %d", driver->driver.name, retval); + udc_controller->gadget.dev.driver = NULL; + udc_controller->driver = NULL; + goto out; + } + + /* Enable DR IRQ reg and Set usbcmd reg Run bit */ + dr_controller_run(udc_controller); + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_state = WAIT_FOR_SETUP; + udc_controller->ep0_dir = 0; + printk(KERN_INFO "%s: bind to driver %s\n", + udc_controller->gadget.name, driver->driver.name); + +out: + if (retval) + printk(KERN_WARNING "gadget driver register failed %d\n", + retval); + return retval; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +/* Disconnect from gadget driver */ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct fsl_ep *loop_ep; + unsigned long flags; + + if (!udc_controller) + return -ENODEV; + + if (!driver || driver != udc_controller->driver || !driver->unbind) + return -EINVAL; + + if (udc_controller->transceiver) + otg_set_peripheral(udc_controller->transceiver, NULL); + + /* stop DR, disable intr */ + dr_controller_stop(udc_controller); + + /* in fact, no needed */ + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_state = WAIT_FOR_SETUP; + udc_controller->ep0_dir = 0; + + /* stand operation */ + spin_lock_irqsave(&udc_controller->lock, flags); + udc_controller->gadget.speed = USB_SPEED_UNKNOWN; + nuke(&udc_controller->eps[0], -ESHUTDOWN); + list_for_each_entry(loop_ep, &udc_controller->gadget.ep_list, + ep.ep_list) + nuke(loop_ep, -ESHUTDOWN); + spin_unlock_irqrestore(&udc_controller->lock, flags); + + /* report disconnect; the controller is already quiesced */ + driver->disconnect(&udc_controller->gadget); + + /* unbind gadget and unhook driver. */ + driver->unbind(&udc_controller->gadget); + udc_controller->gadget.dev.driver = NULL; + udc_controller->driver = NULL; + + printk(KERN_WARNING "unregistered gadget driver '%s'\n", + driver->driver.name); + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/*------------------------------------------------------------------------- + PROC File System Support +-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +#include <linux/seq_file.h> + +static const char proc_filename[] = "driver/fsl_usb2_udc"; + +static int fsl_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + char *buf = page; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t, i; + u32 tmp_reg; + struct fsl_ep *ep = NULL; + struct fsl_req *req; + + struct fsl_udc *udc = udc_controller; + if (off != 0) + return 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* ------basic driver information ---- */ + t = scnprintf(next, size, + DRIVER_DESC "\n" + "%s version: %s\n" + "Gadget driver: %s\n\n", + driver_name, DRIVER_VERSION, + udc->driver ? udc->driver->driver.name : "(none)"); + size -= t; + next += t; + + /* ------ DR Registers ----- */ + tmp_reg = fsl_readl(&dr_regs->usbcmd); + t = scnprintf(next, size, + "USBCMD reg:\n" + "SetupTW: %d\n" + "Run/Stop: %s\n\n", + (tmp_reg & USB_CMD_SUTW) ? 1 : 0, + (tmp_reg & USB_CMD_RUN_STOP) ? "Run" : "Stop"); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->usbsts); + t = scnprintf(next, size, + "USB Status Reg:\n" + "Dr Suspend: %d Reset Received: %d System Error: %s " + "USB Error Interrupt: %s\n\n", + (tmp_reg & USB_STS_SUSPEND) ? 1 : 0, + (tmp_reg & USB_STS_RESET) ? 1 : 0, + (tmp_reg & USB_STS_SYS_ERR) ? "Err" : "Normal", + (tmp_reg & USB_STS_ERR) ? "Err detected" : "No err"); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->usbintr); + t = scnprintf(next, size, + "USB Intrrupt Enable Reg:\n" + "Sleep Enable: %d SOF Received Enable: %d " + "Reset Enable: %d\n" + "System Error Enable: %d " + "Port Change Dectected Enable: %d\n" + "USB Error Intr Enable: %d USB Intr Enable: %d\n\n", + (tmp_reg & USB_INTR_DEVICE_SUSPEND) ? 1 : 0, + (tmp_reg & USB_INTR_SOF_EN) ? 1 : 0, + (tmp_reg & USB_INTR_RESET_EN) ? 1 : 0, + (tmp_reg & USB_INTR_SYS_ERR_EN) ? 1 : 0, + (tmp_reg & USB_INTR_PTC_DETECT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_ERR_INT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_INT_EN) ? 1 : 0); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->frindex); + t = scnprintf(next, size, + "USB Frame Index Reg: Frame Number is 0x%x\n\n", + (tmp_reg & USB_FRINDEX_MASKS)); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->deviceaddr); + t = scnprintf(next, size, + "USB Device Address Reg: Device Addr is 0x%x\n\n", + (tmp_reg & USB_DEVICE_ADDRESS_MASK)); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->endpointlistaddr); + t = scnprintf(next, size, + "USB Endpoint List Address Reg: " + "Device Addr is 0x%x\n\n", + (tmp_reg & USB_EP_LIST_ADDRESS_MASK)); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->portsc1); + t = scnprintf(next, size, + "USB Port Status&Control Reg:\n" + "Port Transceiver Type : %s Port Speed: %s\n" + "PHY Low Power Suspend: %s Port Reset: %s " + "Port Suspend Mode: %s\n" + "Over-current Change: %s " + "Port Enable/Disable Change: %s\n" + "Port Enabled/Disabled: %s " + "Current Connect Status: %s\n\n", ( { + char *s; + switch (tmp_reg & PORTSCX_PTS_FSLS) { + case PORTSCX_PTS_UTMI: + s = "UTMI"; break; + case PORTSCX_PTS_ULPI: + s = "ULPI "; break; + case PORTSCX_PTS_FSLS: + s = "FS/LS Serial"; break; + default: + s = "None"; break; + } + s;} ), ( { + char *s; + switch (tmp_reg & PORTSCX_PORT_SPEED_UNDEF) { + case PORTSCX_PORT_SPEED_FULL: + s = "Full Speed"; break; + case PORTSCX_PORT_SPEED_LOW: + s = "Low Speed"; break; + case PORTSCX_PORT_SPEED_HIGH: + s = "High Speed"; break; + default: + s = "Undefined"; break; + } + s; + } ), + (tmp_reg & PORTSCX_PHY_LOW_POWER_SPD) ? + "Normal PHY mode" : "Low power mode", + (tmp_reg & PORTSCX_PORT_RESET) ? "In Reset" : + "Not in Reset", + (tmp_reg & PORTSCX_PORT_SUSPEND) ? "In " : "Not in", + (tmp_reg & PORTSCX_OVER_CURRENT_CHG) ? "Dected" : + "No", + (tmp_reg & PORTSCX_PORT_EN_DIS_CHANGE) ? "Disable" : + "Not change", + (tmp_reg & PORTSCX_PORT_ENABLE) ? "Enable" : + "Not correct", + (tmp_reg & PORTSCX_CURRENT_CONNECT_STATUS) ? + "Attached" : "Not-Att"); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->usbmode); + t = scnprintf(next, size, + "USB Mode Reg: Controller Mode is: %s\n\n", ( { + char *s; + switch (tmp_reg & USB_MODE_CTRL_MODE_HOST) { + case USB_MODE_CTRL_MODE_IDLE: + s = "Idle"; break; + case USB_MODE_CTRL_MODE_DEVICE: + s = "Device Controller"; break; + case USB_MODE_CTRL_MODE_HOST: + s = "Host Controller"; break; + default: + s = "None"; break; + } + s; + } )); + size -= t; + next += t; + + tmp_reg = fsl_readl(&dr_regs->endptsetupstat); + t = scnprintf(next, size, + "Endpoint Setup Status Reg: SETUP on ep 0x%x\n\n", + (tmp_reg & EP_SETUP_STATUS_MASK)); + size -= t; + next += t; + + for (i = 0; i < udc->max_ep / 2; i++) { + tmp_reg = fsl_readl(&dr_regs->endptctrl[i]); + t = scnprintf(next, size, "EP Ctrl Reg [0x%x]: = [0x%x]\n", + i, tmp_reg); + size -= t; + next += t; + } + tmp_reg = fsl_readl(&dr_regs->endpointprime); + t = scnprintf(next, size, "EP Prime Reg = [0x%x]\n\n", tmp_reg); + size -= t; + next += t; + +#ifndef CONFIG_ARCH_MXC + tmp_reg = usb_sys_regs->snoop1; + t = scnprintf(next, size, "Snoop1 Reg : = [0x%x]\n\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = usb_sys_regs->control; + t = scnprintf(next, size, "General Control Reg : = [0x%x]\n\n", + tmp_reg); + size -= t; + next += t; +#endif + + /* ------fsl_udc, fsl_ep, fsl_request structure information ----- */ + ep = &udc->eps[0]; + t = scnprintf(next, size, "For %s Maxpkt is 0x%x index is 0x%x\n", + ep->ep.name, ep_maxpacket(ep), ep_index(ep)); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length 0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + size -= t; + next += t; + } + } + /* other gadget->eplist ep */ + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + if (ep->desc) { + t = scnprintf(next, size, + "\nFor %s Maxpkt is 0x%x " + "index is 0x%x\n", + ep->ep.name, ep_maxpacket(ep), + ep_index(ep)); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, + "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length " + "0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + size -= t; + next += t; + } /* end for each_entry of ep req */ + } /* end for else */ + } /* end for if(ep->queue) */ + } /* end (ep->desc) */ + + spin_unlock_irqrestore(&udc->lock, flags); + + *eof = 1; + return count - size; +} + +#define create_proc_file() create_proc_read_entry(proc_filename, \ + 0, NULL, fsl_proc_read, NULL) + +#define remove_proc_file() remove_proc_entry(proc_filename, NULL) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_proc_file() do {} while (0) +#define remove_proc_file() do {} while (0) + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +/*-------------------------------------------------------------------------*/ + +/* Release udc structures */ +static void fsl_udc_release(struct device *dev) +{ + complete(udc_controller->done); + dma_free_coherent(dev, udc_controller->ep_qh_size, + udc_controller->ep_qh, udc_controller->ep_qh_dma); + kfree(udc_controller); +} + +/****************************************************************** + Internal structure setup functions +*******************************************************************/ +/*------------------------------------------------------------------ + * init resource for globle controller + * Return the udc handle on success or NULL on failure + ------------------------------------------------------------------*/ +static int __init struct_udc_setup(struct fsl_udc *udc, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + size_t size; + + pdata = pdev->dev.platform_data; + udc->phy_mode = pdata->phy_mode; + + udc->eps = kzalloc(sizeof(struct fsl_ep) * udc->max_ep, GFP_KERNEL); + if (!udc->eps) { + ERR("malloc fsl_ep failed\n"); + return -1; + } + + /* initialized QHs, take care of alignment */ + size = udc->max_ep * sizeof(struct ep_queue_head); + if (size < QH_ALIGNMENT) + size = QH_ALIGNMENT; + else if ((size % QH_ALIGNMENT) != 0) { + size += QH_ALIGNMENT + 1; + size &= ~(QH_ALIGNMENT - 1); + } + udc->ep_qh = dma_alloc_coherent(&pdev->dev, size, + &udc->ep_qh_dma, GFP_KERNEL); + if (!udc->ep_qh) { + ERR("malloc QHs for udc failed\n"); + kfree(udc->eps); + return -1; + } + + udc->ep_qh_size = size; + + /* Initialize ep0 status request structure */ + /* FIXME: fsl_alloc_request() ignores ep argument */ + udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), + struct fsl_req, req); + /* allocate a small amount of memory to get valid address */ + udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); + udc->status_req->req.dma = virt_to_phys(udc->status_req->req.buf); + + udc->resume_state = USB_STATE_NOTATTACHED; + udc->usb_state = USB_STATE_POWERED; + udc->ep0_dir = 0; + udc->remote_wakeup = 0; /* default to 0 on reset */ + + return 0; +} + +/*---------------------------------------------------------------- + * Setup the fsl_ep struct for eps + * Link fsl_ep->ep to gadget->ep_list + * ep0out is not used so do nothing here + * ep0in should be taken care + *--------------------------------------------------------------*/ +static int __init struct_ep_setup(struct fsl_udc *udc, unsigned char index, + char *name, int link) +{ + struct fsl_ep *ep = &udc->eps[index]; + + ep->udc = udc; + strcpy(ep->name, name); + ep->ep.name = ep->name; + + ep->ep.ops = &fsl_ep_ops; + ep->stopped = 0; + + /* for ep0: maxP defined in desc + * for other eps, maxP is set by epautoconfig() called by gadget layer + */ + ep->ep.maxpacket = (unsigned short) ~0; + + /* the queue lists any req for this ep */ + INIT_LIST_HEAD(&ep->queue); + + /* gagdet.ep_list used for ep_autoconfig so no ep0 */ + if (link) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + ep->gadget = &udc->gadget; + ep->qh = &udc->ep_qh[index]; + + return 0; +} + +/* Driver probe function + * all intialization operations implemented here except enabling usb_intr reg + * board setup should have been done in the platform code + */ +static int __init fsl_udc_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret = -ENODEV; + unsigned int i; + u32 dccparams; + + if (strcmp(pdev->name, driver_name)) { + VDBG("Wrong device"); + return -ENODEV; + } + + udc_controller = kzalloc(sizeof(struct fsl_udc), GFP_KERNEL); + if (udc_controller == NULL) { + ERR("malloc udc failed\n"); + return -ENOMEM; + } + + spin_lock_init(&udc_controller->lock); + udc_controller->stopped = 1; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENXIO; + goto err_kfree; + } + + if (!request_mem_region(res->start, res->end - res->start + 1, + driver_name)) { + ERR("request mem region for %s failed\n", pdev->name); + ret = -EBUSY; + goto err_kfree; + } + + dr_regs = ioremap(res->start, resource_size(res)); + if (!dr_regs) { + ret = -ENOMEM; + goto err_release_mem_region; + } + +#ifndef CONFIG_ARCH_MXC + usb_sys_regs = (struct usb_sys_interface *) + ((u32)dr_regs + USB_DR_SYS_OFFSET); +#endif + + /* Initialize USB clocks */ + ret = fsl_udc_clk_init(pdev); + if (ret < 0) + goto err_iounmap_noclk; + + /* Read Device Controller Capability Parameters register */ + dccparams = fsl_readl(&dr_regs->dccparams); + if (!(dccparams & DCCPARAMS_DC)) { + ERR("This SOC doesn't support device role\n"); + ret = -ENODEV; + goto err_iounmap; + } + /* Get max device endpoints */ + /* DEN is bidirectional ep number, max_ep doubles the number */ + udc_controller->max_ep = (dccparams & DCCPARAMS_DEN_MASK) * 2; + + udc_controller->irq = platform_get_irq(pdev, 0); + if (!udc_controller->irq) { + ret = -ENODEV; + goto err_iounmap; + } + + ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED, + driver_name, udc_controller); + if (ret != 0) { + ERR("cannot request irq %d err %d\n", + udc_controller->irq, ret); + goto err_iounmap; + } + + /* Initialize the udc structure including QH member and other member */ + if (struct_udc_setup(udc_controller, pdev)) { + ERR("Can't initialize udc data structure\n"); + ret = -ENOMEM; + goto err_free_irq; + } + + /* initialize usb hw reg except for regs for EP, + * leave usbintr reg untouched */ + dr_controller_setup(udc_controller); + + fsl_udc_clk_finalize(pdev); + + /* Setup gadget structure */ + udc_controller->gadget.ops = &fsl_gadget_ops; + udc_controller->gadget.is_dualspeed = 1; + udc_controller->gadget.ep0 = &udc_controller->eps[0].ep; + INIT_LIST_HEAD(&udc_controller->gadget.ep_list); + udc_controller->gadget.speed = USB_SPEED_UNKNOWN; + udc_controller->gadget.name = driver_name; + + /* Setup gadget.dev and register with kernel */ + dev_set_name(&udc_controller->gadget.dev, "gadget"); + udc_controller->gadget.dev.release = fsl_udc_release; + udc_controller->gadget.dev.parent = &pdev->dev; + ret = device_register(&udc_controller->gadget.dev); + if (ret < 0) + goto err_free_irq; + + /* setup QH and epctrl for ep0 */ + ep0_setup(udc_controller); + + /* setup udc->eps[] for ep0 */ + struct_ep_setup(udc_controller, 0, "ep0", 0); + /* for ep0: the desc defined here; + * for other eps, gadget layer called ep_enable with defined desc + */ + udc_controller->eps[0].desc = &fsl_ep0_desc; + udc_controller->eps[0].ep.maxpacket = USB_MAX_CTRL_PAYLOAD; + + /* setup the udc->eps[] for non-control endpoints and link + * to gadget.ep_list */ + for (i = 1; i < (int)(udc_controller->max_ep / 2); i++) { + char name[14]; + + sprintf(name, "ep%dout", i); + struct_ep_setup(udc_controller, i * 2, name, 1); + sprintf(name, "ep%din", i); + struct_ep_setup(udc_controller, i * 2 + 1, name, 1); + } + + /* use dma_pool for TD management */ + udc_controller->td_pool = dma_pool_create("udc_td", &pdev->dev, + sizeof(struct ep_td_struct), + DTD_ALIGNMENT, UDC_DMA_BOUNDARY); + if (udc_controller->td_pool == NULL) { + ret = -ENOMEM; + goto err_unregister; + } + create_proc_file(); + return 0; + +err_unregister: + device_unregister(&udc_controller->gadget.dev); +err_free_irq: + free_irq(udc_controller->irq, udc_controller); +err_iounmap: + fsl_udc_clk_release(); +err_iounmap_noclk: + iounmap(dr_regs); +err_release_mem_region: + release_mem_region(res->start, res->end - res->start + 1); +err_kfree: + kfree(udc_controller); + udc_controller = NULL; + return ret; +} + +/* Driver removal function + * Free resources and finish pending transactions + */ +static int __exit fsl_udc_remove(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + DECLARE_COMPLETION(done); + + if (!udc_controller) + return -ENODEV; + udc_controller->done = &done; + + fsl_udc_clk_release(); + + /* DR has been stopped in usb_gadget_unregister_driver() */ + remove_proc_file(); + + /* Free allocated memory */ + kfree(udc_controller->status_req->req.buf); + kfree(udc_controller->status_req); + kfree(udc_controller->eps); + + dma_pool_destroy(udc_controller->td_pool); + free_irq(udc_controller->irq, udc_controller); + iounmap(dr_regs); + release_mem_region(res->start, res->end - res->start + 1); + + device_unregister(&udc_controller->gadget.dev); + /* free udc --wait for the release() finished */ + wait_for_completion(&done); + + return 0; +} + +/*----------------------------------------------------------------- + * Modify Power management attributes + * Used by OTG statemachine to disable gadget temporarily + -----------------------------------------------------------------*/ +static int fsl_udc_suspend(struct platform_device *pdev, pm_message_t state) +{ + dr_controller_stop(udc_controller); + return 0; +} + +/*----------------------------------------------------------------- + * Invoked on USB resume. May be called in_interrupt. + * Here we start the DR controller and enable the irq + *-----------------------------------------------------------------*/ +static int fsl_udc_resume(struct platform_device *pdev) +{ + /* Enable DR irq reg and set controller Run */ + if (udc_controller->stopped) { + dr_controller_setup(udc_controller); + dr_controller_run(udc_controller); + } + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_state = WAIT_FOR_SETUP; + udc_controller->ep0_dir = 0; + return 0; +} + +/*------------------------------------------------------------------------- + Register entry point for the peripheral controller driver +--------------------------------------------------------------------------*/ + +static struct platform_driver udc_driver = { + .remove = __exit_p(fsl_udc_remove), + /* these suspend and resume are not usb suspend and resume */ + .suspend = fsl_udc_suspend, + .resume = fsl_udc_resume, + .driver = { + .name = (char *)driver_name, + .owner = THIS_MODULE, + }, +}; + +static int __init udc_init(void) +{ + printk(KERN_INFO "%s (%s)\n", driver_desc, DRIVER_VERSION); + return platform_driver_probe(&udc_driver, fsl_udc_probe); +} + +module_init(udc_init); + +static void __exit udc_exit(void) +{ + platform_driver_unregister(&udc_driver); + printk(KERN_WARNING "%s unregistered\n", driver_desc); +} + +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:fsl-usb2-udc"); diff --git a/drivers/usb/gadget/fsl_usb2_udc.c b/drivers/usb/gadget/fsl_usb2_udc.c deleted file mode 100644 index 9d7b95d4e3d2..000000000000 --- a/drivers/usb/gadget/fsl_usb2_udc.c +++ /dev/null @@ -1,2468 +0,0 @@ -/* - * Copyright (C) 2004-2007 Freescale Semicondutor, Inc. All rights reserved. - * - * Author: Li Yang <leoli@freescale.com> - * Jiang Bo <tanya.jiang@freescale.com> - * - * Description: - * Freescale high-speed USB SOC DR module device controller driver. - * This can be found on MPC8349E/MPC8313E cpus. - * The driver is previously named as mpc_udc. Based on bare board - * code from Dave Liu and Shlomi Gridish. - * - * 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 VERBOSE - -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/ioport.h> -#include <linux/types.h> -#include <linux/errno.h> -#include <linux/slab.h> -#include <linux/init.h> -#include <linux/list.h> -#include <linux/interrupt.h> -#include <linux/proc_fs.h> -#include <linux/mm.h> -#include <linux/moduleparam.h> -#include <linux/device.h> -#include <linux/usb/ch9.h> -#include <linux/usb/gadget.h> -#include <linux/usb/otg.h> -#include <linux/dma-mapping.h> -#include <linux/platform_device.h> -#include <linux/fsl_devices.h> -#include <linux/dmapool.h> - -#include <asm/byteorder.h> -#include <asm/io.h> -#include <asm/system.h> -#include <asm/unaligned.h> -#include <asm/dma.h> - -#include "fsl_usb2_udc.h" - -#define DRIVER_DESC "Freescale High-Speed USB SOC Device Controller driver" -#define DRIVER_AUTHOR "Li Yang/Jiang Bo" -#define DRIVER_VERSION "Apr 20, 2007" - -#define DMA_ADDR_INVALID (~(dma_addr_t)0) - -static const char driver_name[] = "fsl-usb2-udc"; -static const char driver_desc[] = DRIVER_DESC; - -static struct usb_dr_device *dr_regs; -static struct usb_sys_interface *usb_sys_regs; - -/* it is initialized in probe() */ -static struct fsl_udc *udc_controller = NULL; - -static const struct usb_endpoint_descriptor -fsl_ep0_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = 0, - .bmAttributes = USB_ENDPOINT_XFER_CONTROL, - .wMaxPacketSize = USB_MAX_CTRL_PAYLOAD, -}; - -static void fsl_ep_fifo_flush(struct usb_ep *_ep); - -#ifdef CONFIG_PPC32 -#define fsl_readl(addr) in_le32(addr) -#define fsl_writel(val32, addr) out_le32(addr, val32) -#else -#define fsl_readl(addr) readl(addr) -#define fsl_writel(val32, addr) writel(val32, addr) -#endif - -/******************************************************************** - * Internal Used Function -********************************************************************/ -/*----------------------------------------------------------------- - * done() - retire a request; caller blocked irqs - * @status : request status to be set, only works when - * request is still in progress. - *--------------------------------------------------------------*/ -static void done(struct fsl_ep *ep, struct fsl_req *req, int status) -{ - struct fsl_udc *udc = NULL; - unsigned char stopped = ep->stopped; - struct ep_td_struct *curr_td, *next_td; - int j; - - udc = (struct fsl_udc *)ep->udc; - /* Removed the req from fsl_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; - - /* Free dtd for the request */ - next_td = req->head; - for (j = 0; j < req->dtd_count; j++) { - curr_td = next_td; - if (j != req->dtd_count - 1) { - next_td = curr_td->next_td_virt; - } - dma_pool_free(udc->td_pool, curr_td, curr_td->td_dma); - } - - if (req->mapped) { - dma_unmap_single(ep->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(ep->udc->gadget.dev.parent, - req->req.dma, req->req.length, - ep_is_in(ep) - ? DMA_TO_DEVICE - : DMA_FROM_DEVICE); - - if (status && (status != -ESHUTDOWN)) - VDBG("complete %s req %p stat %d len %u/%u", - ep->ep.name, &req->req, status, - req->req.actual, req->req.length); - - ep->stopped = 1; - - spin_unlock(&ep->udc->lock); - /* complete() is from gadget layer, - * eg fsg->bulk_in_complete() */ - if (req->req.complete) - req->req.complete(&ep->ep, &req->req); - - spin_lock(&ep->udc->lock); - ep->stopped = stopped; -} - -/*----------------------------------------------------------------- - * nuke(): delete all requests related to this ep - * called with spinlock held - *--------------------------------------------------------------*/ -static void nuke(struct fsl_ep *ep, int status) -{ - ep->stopped = 1; - - /* Flush fifo */ - fsl_ep_fifo_flush(&ep->ep); - - /* Whether this eq has request linked */ - while (!list_empty(&ep->queue)) { - struct fsl_req *req = NULL; - - req = list_entry(ep->queue.next, struct fsl_req, queue); - done(ep, req, status); - } -} - -/*------------------------------------------------------------------ - Internal Hardware related function - ------------------------------------------------------------------*/ - -static int dr_controller_setup(struct fsl_udc *udc) -{ - unsigned int tmp = 0, portctrl = 0, ctrl = 0; - unsigned long timeout; -#define FSL_UDC_RESET_TIMEOUT 1000 - - /* Stop and reset the usb controller */ - tmp = fsl_readl(&dr_regs->usbcmd); - tmp &= ~USB_CMD_RUN_STOP; - fsl_writel(tmp, &dr_regs->usbcmd); - - tmp = fsl_readl(&dr_regs->usbcmd); - tmp |= USB_CMD_CTRL_RESET; - fsl_writel(tmp, &dr_regs->usbcmd); - - /* Wait for reset to complete */ - timeout = jiffies + FSL_UDC_RESET_TIMEOUT; - while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) { - if (time_after(jiffies, timeout)) { - ERR("udc reset timeout!\n"); - return -ETIMEDOUT; - } - cpu_relax(); - } - - /* Set the controller as device mode */ - tmp = fsl_readl(&dr_regs->usbmode); - tmp |= USB_MODE_CTRL_MODE_DEVICE; - /* Disable Setup Lockout */ - tmp |= USB_MODE_SETUP_LOCK_OFF; - fsl_writel(tmp, &dr_regs->usbmode); - - /* Clear the setup status */ - fsl_writel(0, &dr_regs->usbsts); - - tmp = udc->ep_qh_dma; - tmp &= USB_EP_LIST_ADDRESS_MASK; - fsl_writel(tmp, &dr_regs->endpointlistaddr); - - VDBG("vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x", - udc->ep_qh, (int)tmp, - fsl_readl(&dr_regs->endpointlistaddr)); - - /* Config PHY interface */ - portctrl = fsl_readl(&dr_regs->portsc1); - portctrl &= ~(PORTSCX_PHY_TYPE_SEL | PORTSCX_PORT_WIDTH); - switch (udc->phy_mode) { - case FSL_USB2_PHY_ULPI: - portctrl |= PORTSCX_PTS_ULPI; - break; - case FSL_USB2_PHY_UTMI_WIDE: - portctrl |= PORTSCX_PTW_16BIT; - /* fall through */ - case FSL_USB2_PHY_UTMI: - portctrl |= PORTSCX_PTS_UTMI; - break; - case FSL_USB2_PHY_SERIAL: - portctrl |= PORTSCX_PTS_FSLS; - break; - default: - return -EINVAL; - } - fsl_writel(portctrl, &dr_regs->portsc1); - - /* Config control enable i/o output, cpu endian register */ - ctrl = __raw_readl(&usb_sys_regs->control); - ctrl |= USB_CTRL_IOENB; - __raw_writel(ctrl, &usb_sys_regs->control); - -#if defined(CONFIG_PPC32) && !defined(CONFIG_NOT_COHERENT_CACHE) - /* Turn on cache snooping hardware, since some PowerPC platforms - * wholly rely on hardware to deal with cache coherent. */ - - /* Setup Snooping for all the 4GB space */ - tmp = SNOOP_SIZE_2GB; /* starts from 0x0, size 2G */ - __raw_writel(tmp, &usb_sys_regs->snoop1); - tmp |= 0x80000000; /* starts from 0x8000000, size 2G */ - __raw_writel(tmp, &usb_sys_regs->snoop2); -#endif - - return 0; -} - -/* Enable DR irq and set controller to run state */ -static void dr_controller_run(struct fsl_udc *udc) -{ - u32 temp; - - /* Enable DR irq reg */ - temp = USB_INTR_INT_EN | USB_INTR_ERR_INT_EN - | USB_INTR_PTC_DETECT_EN | USB_INTR_RESET_EN - | USB_INTR_DEVICE_SUSPEND | USB_INTR_SYS_ERR_EN; - - fsl_writel(temp, &dr_regs->usbintr); - - /* Clear stopped bit */ - udc->stopped = 0; - - /* Set the controller as device mode */ - temp = fsl_readl(&dr_regs->usbmode); - temp |= USB_MODE_CTRL_MODE_DEVICE; - fsl_writel(temp, &dr_regs->usbmode); - - /* Set controller to Run */ - temp = fsl_readl(&dr_regs->usbcmd); - temp |= USB_CMD_RUN_STOP; - fsl_writel(temp, &dr_regs->usbcmd); - - return; -} - -static void dr_controller_stop(struct fsl_udc *udc) -{ - unsigned int tmp; - - /* disable all INTR */ - fsl_writel(0, &dr_regs->usbintr); - - /* Set stopped bit for isr */ - udc->stopped = 1; - - /* disable IO output */ -/* usb_sys_regs->control = 0; */ - - /* set controller to Stop */ - tmp = fsl_readl(&dr_regs->usbcmd); - tmp &= ~USB_CMD_RUN_STOP; - fsl_writel(tmp, &dr_regs->usbcmd); - - return; -} - -static void dr_ep_setup(unsigned char ep_num, unsigned char dir, - unsigned char ep_type) -{ - unsigned int tmp_epctrl = 0; - - tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); - if (dir) { - if (ep_num) - tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; - tmp_epctrl |= EPCTRL_TX_ENABLE; - tmp_epctrl |= ((unsigned int)(ep_type) - << EPCTRL_TX_EP_TYPE_SHIFT); - } else { - if (ep_num) - tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; - tmp_epctrl |= EPCTRL_RX_ENABLE; - tmp_epctrl |= ((unsigned int)(ep_type) - << EPCTRL_RX_EP_TYPE_SHIFT); - } - - fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]); -} - -static void -dr_ep_change_stall(unsigned char ep_num, unsigned char dir, int value) -{ - u32 tmp_epctrl = 0; - - tmp_epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); - - if (value) { - /* set the stall bit */ - if (dir) - tmp_epctrl |= EPCTRL_TX_EP_STALL; - else - tmp_epctrl |= EPCTRL_RX_EP_STALL; - } else { - /* clear the stall bit and reset data toggle */ - if (dir) { - tmp_epctrl &= ~EPCTRL_TX_EP_STALL; - tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; - } else { - tmp_epctrl &= ~EPCTRL_RX_EP_STALL; - tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; - } - } - fsl_writel(tmp_epctrl, &dr_regs->endptctrl[ep_num]); -} - -/* Get stall status of a specific ep - Return: 0: not stalled; 1:stalled */ -static int dr_ep_get_stall(unsigned char ep_num, unsigned char dir) -{ - u32 epctrl; - - epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); - if (dir) - return (epctrl & EPCTRL_TX_EP_STALL) ? 1 : 0; - else - return (epctrl & EPCTRL_RX_EP_STALL) ? 1 : 0; -} - -/******************************************************************** - Internal Structure Build up functions -********************************************************************/ - -/*------------------------------------------------------------------ -* struct_ep_qh_setup(): set the Endpoint Capabilites field of QH - * @zlt: Zero Length Termination Select (1: disable; 0: enable) - * @mult: Mult field - ------------------------------------------------------------------*/ -static void struct_ep_qh_setup(struct fsl_udc *udc, unsigned char ep_num, - unsigned char dir, unsigned char ep_type, - unsigned int max_pkt_len, - unsigned int zlt, unsigned char mult) -{ - struct ep_queue_head *p_QH = &udc->ep_qh[2 * ep_num + dir]; - unsigned int tmp = 0; - - /* set the Endpoint Capabilites in QH */ - switch (ep_type) { - case USB_ENDPOINT_XFER_CONTROL: - /* Interrupt On Setup (IOS). for control ep */ - tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) - | EP_QUEUE_HEAD_IOS; - break; - case USB_ENDPOINT_XFER_ISOC: - tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) - | (mult << EP_QUEUE_HEAD_MULT_POS); - break; - case USB_ENDPOINT_XFER_BULK: - case USB_ENDPOINT_XFER_INT: - tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; - break; - default: - VDBG("error ep type is %d", ep_type); - return; - } - if (zlt) - tmp |= EP_QUEUE_HEAD_ZLT_SEL; - - p_QH->max_pkt_length = cpu_to_le32(tmp); - p_QH->next_dtd_ptr = 1; - p_QH->size_ioc_int_sts = 0; - - return; -} - -/* Setup qh structure and ep register for ep0. */ -static void ep0_setup(struct fsl_udc *udc) -{ - /* the intialization of an ep includes: fields in QH, Regs, - * fsl_ep struct */ - struct_ep_qh_setup(udc, 0, USB_RECV, USB_ENDPOINT_XFER_CONTROL, - USB_MAX_CTRL_PAYLOAD, 0, 0); - struct_ep_qh_setup(udc, 0, USB_SEND, USB_ENDPOINT_XFER_CONTROL, - USB_MAX_CTRL_PAYLOAD, 0, 0); - dr_ep_setup(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL); - dr_ep_setup(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL); - - return; - -} - -/*********************************************************************** - Endpoint Management Functions -***********************************************************************/ - -/*------------------------------------------------------------------------- - * when configurations are set, or when interface settings change - * for example the do_set_interface() in gadget layer, - * the driver will enable or disable the relevant endpoints - * ep0 doesn't use this routine. It is always enabled. --------------------------------------------------------------------------*/ -static int fsl_ep_enable(struct usb_ep *_ep, - const struct usb_endpoint_descriptor *desc) -{ - struct fsl_udc *udc = NULL; - struct fsl_ep *ep = NULL; - unsigned short max = 0; - unsigned char mult = 0, zlt; - int retval = -EINVAL; - unsigned long flags = 0; - - ep = container_of(_ep, struct fsl_ep, ep); - - /* catch various bogus parameters */ - if (!_ep || !desc || ep->desc - || (desc->bDescriptorType != USB_DT_ENDPOINT)) - return -EINVAL; - - udc = ep->udc; - - if (!udc->driver || (udc->gadget.speed == USB_SPEED_UNKNOWN)) - return -ESHUTDOWN; - - max = le16_to_cpu(desc->wMaxPacketSize); - - /* Disable automatic zlp generation. Driver is reponsible to indicate - * explicitly through req->req.zero. This is needed to enable multi-td - * request. */ - zlt = 1; - - /* Assume the max packet size from gadget is always correct */ - switch (desc->bmAttributes & 0x03) { - case USB_ENDPOINT_XFER_CONTROL: - case USB_ENDPOINT_XFER_BULK: - case USB_ENDPOINT_XFER_INT: - /* mult = 0. Execute N Transactions as demonstrated by - * the USB variable length packet protocol where N is - * computed using the Maximum Packet Length (dQH) and - * the Total Bytes field (dTD) */ - mult = 0; - break; - case USB_ENDPOINT_XFER_ISOC: - /* Calculate transactions needed for high bandwidth iso */ - mult = (unsigned char)(1 + ((max >> 11) & 0x03)); - max = max & 0x8ff; /* bit 0~10 */ - /* 3 transactions at most */ - if (mult > 3) - goto en_done; - break; - default: - goto en_done; - } - - spin_lock_irqsave(&udc->lock, flags); - ep->ep.maxpacket = max; - ep->desc = desc; - ep->stopped = 0; - - /* Controller related setup */ - /* Init EPx Queue Head (Ep Capabilites field in QH - * according to max, zlt, mult) */ - struct_ep_qh_setup(udc, (unsigned char) ep_index(ep), - (unsigned char) ((desc->bEndpointAddress & USB_DIR_IN) - ? USB_SEND : USB_RECV), - (unsigned char) (desc->bmAttributes - & USB_ENDPOINT_XFERTYPE_MASK), - max, zlt, mult); - - /* Init endpoint ctrl register */ - dr_ep_setup((unsigned char) ep_index(ep), - (unsigned char) ((desc->bEndpointAddress & USB_DIR_IN) - ? USB_SEND : USB_RECV), - (unsigned char) (desc->bmAttributes - & USB_ENDPOINT_XFERTYPE_MASK)); - - spin_unlock_irqrestore(&udc->lock, flags); - retval = 0; - - VDBG("enabled %s (ep%d%s) maxpacket %d",ep->ep.name, - ep->desc->bEndpointAddress & 0x0f, - (desc->bEndpointAddress & USB_DIR_IN) - ? "in" : "out", max); -en_done: - return retval; -} - -/*--------------------------------------------------------------------- - * @ep : the ep being unconfigured. May not be ep0 - * Any pending and uncomplete req will complete with status (-ESHUTDOWN) -*---------------------------------------------------------------------*/ -static int fsl_ep_disable(struct usb_ep *_ep) -{ - struct fsl_udc *udc = NULL; - struct fsl_ep *ep = NULL; - unsigned long flags = 0; - u32 epctrl; - int ep_num; - - ep = container_of(_ep, struct fsl_ep, ep); - if (!_ep || !ep->desc) { - VDBG("%s not enabled", _ep ? ep->ep.name : NULL); - return -EINVAL; - } - - /* disable ep on controller */ - ep_num = ep_index(ep); - epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); - if (ep_is_in(ep)) - epctrl &= ~EPCTRL_TX_ENABLE; - else - epctrl &= ~EPCTRL_RX_ENABLE; - fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); - - udc = (struct fsl_udc *)ep->udc; - spin_lock_irqsave(&udc->lock, flags); - - /* nuke all pending requests (does flush) */ - nuke(ep, -ESHUTDOWN); - - ep->desc = NULL; - ep->stopped = 1; - spin_unlock_irqrestore(&udc->lock, flags); - - VDBG("disabled %s OK", _ep->name); - return 0; -} - -/*--------------------------------------------------------------------- - * allocate a request object used by this endpoint - * the main operation is to insert the req->queue to the eq->queue - * Returns the request, or null if one could not be allocated -*---------------------------------------------------------------------*/ -static struct usb_request * -fsl_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) -{ - struct fsl_req *req = NULL; - - req = kzalloc(sizeof *req, gfp_flags); - if (!req) - return NULL; - - req->req.dma = DMA_ADDR_INVALID; - INIT_LIST_HEAD(&req->queue); - - return &req->req; -} - -static void fsl_free_request(struct usb_ep *_ep, struct usb_request *_req) -{ - struct fsl_req *req = NULL; - - req = container_of(_req, struct fsl_req, req); - - if (_req) - kfree(req); -} - -/*-------------------------------------------------------------------------*/ -static void fsl_queue_td(struct fsl_ep *ep, struct fsl_req *req) -{ - int i = ep_index(ep) * 2 + ep_is_in(ep); - u32 temp, bitmask, tmp_stat; - struct ep_queue_head *dQH = &ep->udc->ep_qh[i]; - - /* VDBG("QH addr Register 0x%8x", dr_regs->endpointlistaddr); - VDBG("ep_qh[%d] addr is 0x%8x", i, (u32)&(ep->udc->ep_qh[i])); */ - - bitmask = ep_is_in(ep) - ? (1 << (ep_index(ep) + 16)) - : (1 << (ep_index(ep))); - - /* check if the pipe is empty */ - if (!(list_empty(&ep->queue))) { - /* Add td to the end */ - struct fsl_req *lastreq; - lastreq = list_entry(ep->queue.prev, struct fsl_req, queue); - lastreq->tail->next_td_ptr = - cpu_to_le32(req->head->td_dma & DTD_ADDR_MASK); - /* Read prime bit, if 1 goto done */ - if (fsl_readl(&dr_regs->endpointprime) & bitmask) - goto out; - - do { - /* Set ATDTW bit in USBCMD */ - temp = fsl_readl(&dr_regs->usbcmd); - fsl_writel(temp | USB_CMD_ATDTW, &dr_regs->usbcmd); - - /* Read correct status bit */ - tmp_stat = fsl_readl(&dr_regs->endptstatus) & bitmask; - - } while (!(fsl_readl(&dr_regs->usbcmd) & USB_CMD_ATDTW)); - - /* Write ATDTW bit to 0 */ - temp = fsl_readl(&dr_regs->usbcmd); - fsl_writel(temp & ~USB_CMD_ATDTW, &dr_regs->usbcmd); - - if (tmp_stat) - goto out; - } - - /* Write dQH next pointer and terminate bit to 0 */ - temp = req->head->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK; - dQH->next_dtd_ptr = cpu_to_le32(temp); - - /* Clear active and halt bit */ - temp = cpu_to_le32(~(EP_QUEUE_HEAD_STATUS_ACTIVE - | EP_QUEUE_HEAD_STATUS_HALT)); - dQH->size_ioc_int_sts &= temp; - - /* Ensure that updates to the QH will occure before priming. */ - wmb(); - - /* Prime endpoint by writing 1 to ENDPTPRIME */ - temp = ep_is_in(ep) - ? (1 << (ep_index(ep) + 16)) - : (1 << (ep_index(ep))); - fsl_writel(temp, &dr_regs->endpointprime); -out: - return; -} - -/* Fill in the dTD structure - * @req: request that the transfer belongs to - * @length: return actually data length of the dTD - * @dma: return dma address of the dTD - * @is_last: return flag if it is the last dTD of the request - * return: pointer to the built dTD */ -static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, - dma_addr_t *dma, int *is_last) -{ - u32 swap_temp; - struct ep_td_struct *dtd; - - /* how big will this transfer be? */ - *length = min(req->req.length - req->req.actual, - (unsigned)EP_MAX_LENGTH_TRANSFER); - - dtd = dma_pool_alloc(udc_controller->td_pool, GFP_KERNEL, dma); - if (dtd == NULL) - return dtd; - - dtd->td_dma = *dma; - /* Clear reserved field */ - swap_temp = cpu_to_le32(dtd->size_ioc_sts); - swap_temp &= ~DTD_RESERVED_FIELDS; - dtd->size_ioc_sts = cpu_to_le32(swap_temp); - - /* Init all of buffer page pointers */ - swap_temp = (u32) (req->req.dma + req->req.actual); - dtd->buff_ptr0 = cpu_to_le32(swap_temp); - dtd->buff_ptr1 = cpu_to_le32(swap_temp + 0x1000); - dtd->buff_ptr2 = cpu_to_le32(swap_temp + 0x2000); - dtd->buff_ptr3 = cpu_to_le32(swap_temp + 0x3000); - dtd->buff_ptr4 = cpu_to_le32(swap_temp + 0x4000); - - req->req.actual += *length; - - /* zlp is needed if req->req.zero is set */ - if (req->req.zero) { - if (*length == 0 || (*length % req->ep->ep.maxpacket) != 0) - *is_last = 1; - else - *is_last = 0; - } else if (req->req.length == req->req.actual) - *is_last = 1; - else - *is_last = 0; - - if ((*is_last) == 0) - VDBG("multi-dtd request!"); - /* Fill in the transfer size; set active bit */ - swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE); - - /* Enable interrupt for the last dtd of a request */ - if (*is_last && !req->req.no_interrupt) - swap_temp |= DTD_IOC; - - dtd->size_ioc_sts = cpu_to_le32(swap_temp); - - mb(); - - VDBG("length = %d address= 0x%x", *length, (int)*dma); - - return dtd; -} - -/* Generate dtd chain for a request */ -static int fsl_req_to_dtd(struct fsl_req *req) -{ - unsigned count; - int is_last; - int is_first =1; - struct ep_td_struct *last_dtd = NULL, *dtd; - dma_addr_t dma; - - do { - dtd = fsl_build_dtd(req, &count, &dma, &is_last); - if (dtd == NULL) - return -ENOMEM; - - if (is_first) { - is_first = 0; - req->head = dtd; - } else { - last_dtd->next_td_ptr = cpu_to_le32(dma); - last_dtd->next_td_virt = dtd; - } - last_dtd = dtd; - - req->dtd_count++; - } while (!is_last); - - dtd->next_td_ptr = cpu_to_le32(DTD_NEXT_TERMINATE); - - req->tail = dtd; - - return 0; -} - -/* queues (submits) an I/O request to an endpoint */ -static int -fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) -{ - struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); - struct fsl_req *req = container_of(_req, struct fsl_req, req); - struct fsl_udc *udc; - unsigned long flags; - int is_iso = 0; - - /* catch various bogus parameters */ - if (!_req || !req->req.complete || !req->req.buf - || !list_empty(&req->queue)) { - VDBG("%s, bad params", __func__); - return -EINVAL; - } - if (unlikely(!_ep || !ep->desc)) { - VDBG("%s, bad ep", __func__); - return -EINVAL; - } - if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { - if (req->req.length > ep->ep.maxpacket) - return -EMSGSIZE; - is_iso = 1; - } - - udc = ep->udc; - if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - req->ep = ep; - - /* map virtual address to hardware */ - if (req->req.dma == DMA_ADDR_INVALID) { - req->req.dma = dma_map_single(ep->udc->gadget.dev.parent, - req->req.buf, - req->req.length, ep_is_in(ep) - ? DMA_TO_DEVICE - : DMA_FROM_DEVICE); - req->mapped = 1; - } else { - dma_sync_single_for_device(ep->udc->gadget.dev.parent, - req->req.dma, req->req.length, - ep_is_in(ep) - ? DMA_TO_DEVICE - : DMA_FROM_DEVICE); - req->mapped = 0; - } - - req->req.status = -EINPROGRESS; - req->req.actual = 0; - req->dtd_count = 0; - - spin_lock_irqsave(&udc->lock, flags); - - /* build dtds and push them to device queue */ - if (!fsl_req_to_dtd(req)) { - fsl_queue_td(ep, req); - } else { - spin_unlock_irqrestore(&udc->lock, flags); - return -ENOMEM; - } - - /* Update ep0 state */ - if ((ep_index(ep) == 0)) - udc->ep0_state = DATA_STATE_XMIT; - - /* irq handler advances the queue */ - if (req != NULL) - list_add_tail(&req->queue, &ep->queue); - spin_unlock_irqrestore(&udc->lock, flags); - - return 0; -} - -/* dequeues (cancels, unlinks) an I/O request from an endpoint */ -static int fsl_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) -{ - struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); - struct fsl_req *req; - unsigned long flags; - int ep_num, stopped, ret = 0; - u32 epctrl; - - if (!_ep || !_req) - return -EINVAL; - - spin_lock_irqsave(&ep->udc->lock, flags); - stopped = ep->stopped; - - /* Stop the ep before we deal with the queue */ - ep->stopped = 1; - ep_num = ep_index(ep); - epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); - if (ep_is_in(ep)) - epctrl &= ~EPCTRL_TX_ENABLE; - else - epctrl &= ~EPCTRL_RX_ENABLE; - fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); - - /* 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) { - ret = -EINVAL; - goto out; - } - - /* The request is in progress, or completed but not dequeued */ - if (ep->queue.next == &req->queue) { - _req->status = -ECONNRESET; - fsl_ep_fifo_flush(_ep); /* flush current transfer */ - - /* The request isn't the last request in this ep queue */ - if (req->queue.next != &ep->queue) { - struct ep_queue_head *qh; - struct fsl_req *next_req; - - qh = ep->qh; - next_req = list_entry(req->queue.next, struct fsl_req, - queue); - - /* Point the QH to the first TD of next request */ - fsl_writel((u32) next_req->head, &qh->curr_dtd_ptr); - } - - /* The request hasn't been processed, patch up the TD chain */ - } else { - struct fsl_req *prev_req; - - prev_req = list_entry(req->queue.prev, struct fsl_req, queue); - fsl_writel(fsl_readl(&req->tail->next_td_ptr), - &prev_req->tail->next_td_ptr); - - } - - done(ep, req, -ECONNRESET); - - /* Enable EP */ -out: epctrl = fsl_readl(&dr_regs->endptctrl[ep_num]); - if (ep_is_in(ep)) - epctrl |= EPCTRL_TX_ENABLE; - else - epctrl |= EPCTRL_RX_ENABLE; - fsl_writel(epctrl, &dr_regs->endptctrl[ep_num]); - ep->stopped = stopped; - - spin_unlock_irqrestore(&ep->udc->lock, flags); - return ret; -} - -/*-------------------------------------------------------------------------*/ - -/*----------------------------------------------------------------- - * modify the endpoint halt feature - * @ep: the non-isochronous endpoint being stalled - * @value: 1--set halt 0--clear halt - * Returns zero, or a negative error code. -*----------------------------------------------------------------*/ -static int fsl_ep_set_halt(struct usb_ep *_ep, int value) -{ - struct fsl_ep *ep = NULL; - unsigned long flags = 0; - int status = -EOPNOTSUPP; /* operation not supported */ - unsigned char ep_dir = 0, ep_num = 0; - struct fsl_udc *udc = NULL; - - ep = container_of(_ep, struct fsl_ep, ep); - udc = ep->udc; - if (!_ep || !ep->desc) { - status = -EINVAL; - goto out; - } - - if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { - status = -EOPNOTSUPP; - goto out; - } - - /* Attempt to halt IN ep will fail if any transfer requests - * are still queue */ - if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { - status = -EAGAIN; - goto out; - } - - status = 0; - ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; - ep_num = (unsigned char)(ep_index(ep)); - spin_lock_irqsave(&ep->udc->lock, flags); - dr_ep_change_stall(ep_num, ep_dir, value); - spin_unlock_irqrestore(&ep->udc->lock, flags); - - if (ep_index(ep) == 0) { - udc->ep0_state = WAIT_FOR_SETUP; - udc->ep0_dir = 0; - } -out: - VDBG(" %s %s halt stat %d", ep->ep.name, - value ? "set" : "clear", status); - - return status; -} - -static void fsl_ep_fifo_flush(struct usb_ep *_ep) -{ - struct fsl_ep *ep; - int ep_num, ep_dir; - u32 bits; - unsigned long timeout; -#define FSL_UDC_FLUSH_TIMEOUT 1000 - - if (!_ep) { - return; - } else { - ep = container_of(_ep, struct fsl_ep, ep); - if (!ep->desc) - return; - } - ep_num = ep_index(ep); - ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; - - if (ep_num == 0) - bits = (1 << 16) | 1; - else if (ep_dir == USB_SEND) - bits = 1 << (16 + ep_num); - else - bits = 1 << ep_num; - - timeout = jiffies + FSL_UDC_FLUSH_TIMEOUT; - do { - fsl_writel(bits, &dr_regs->endptflush); - - /* Wait until flush complete */ - while (fsl_readl(&dr_regs->endptflush)) { - if (time_after(jiffies, timeout)) { - ERR("ep flush timeout\n"); - return; - } - cpu_relax(); - } - /* See if we need to flush again */ - } while (fsl_readl(&dr_regs->endptstatus) & bits); -} - -static struct usb_ep_ops fsl_ep_ops = { - .enable = fsl_ep_enable, - .disable = fsl_ep_disable, - - .alloc_request = fsl_alloc_request, - .free_request = fsl_free_request, - - .queue = fsl_ep_queue, - .dequeue = fsl_ep_dequeue, - - .set_halt = fsl_ep_set_halt, - .fifo_flush = fsl_ep_fifo_flush, /* flush fifo */ -}; - -/*------------------------------------------------------------------------- - Gadget Driver Layer Operations --------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------- - * Get the current frame number (from DR frame_index Reg ) - *----------------------------------------------------------------------*/ -static int fsl_get_frame(struct usb_gadget *gadget) -{ - return (int)(fsl_readl(&dr_regs->frindex) & USB_FRINDEX_MASKS); -} - -/*----------------------------------------------------------------------- - * Tries to wake up the host connected to this gadget - -----------------------------------------------------------------------*/ -static int fsl_wakeup(struct usb_gadget *gadget) -{ - struct fsl_udc *udc = container_of(gadget, struct fsl_udc, gadget); - u32 portsc; - - /* Remote wakeup feature not enabled by host */ - if (!udc->remote_wakeup) - return -ENOTSUPP; - - portsc = fsl_readl(&dr_regs->portsc1); - /* not suspended? */ - if (!(portsc & PORTSCX_PORT_SUSPEND)) - return 0; - /* trigger force resume */ - portsc |= PORTSCX_PORT_FORCE_RESUME; - fsl_writel(portsc, &dr_regs->portsc1); - return 0; -} - -static int can_pullup(struct fsl_udc *udc) -{ - return udc->driver && udc->softconnect && udc->vbus_active; -} - -/* Notify controller that VBUS is powered, Called by whatever - detects VBUS sessions */ -static int fsl_vbus_session(struct usb_gadget *gadget, int is_active) -{ - struct fsl_udc *udc; - unsigned long flags; - - udc = container_of(gadget, struct fsl_udc, gadget); - spin_lock_irqsave(&udc->lock, flags); - VDBG("VBUS %s", is_active ? "on" : "off"); - udc->vbus_active = (is_active != 0); - if (can_pullup(udc)) - fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), - &dr_regs->usbcmd); - else - fsl_writel((fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP), - &dr_regs->usbcmd); - spin_unlock_irqrestore(&udc->lock, flags); - return 0; -} - -/* constrain controller's VBUS power usage - * This call is used by gadget drivers during SET_CONFIGURATION calls, - * reporting how much power the device may consume. For example, this - * could affect how quickly batteries are recharged. - * - * Returns zero on success, else negative errno. - */ -static int fsl_vbus_draw(struct usb_gadget *gadget, unsigned mA) -{ - struct fsl_udc *udc; - - udc = container_of(gadget, struct fsl_udc, gadget); - if (udc->transceiver) - return otg_set_power(udc->transceiver, mA); - return -ENOTSUPP; -} - -/* Change Data+ pullup status - * this func is used by usb_gadget_connect/disconnet - */ -static int fsl_pullup(struct usb_gadget *gadget, int is_on) -{ - struct fsl_udc *udc; - - udc = container_of(gadget, struct fsl_udc, gadget); - udc->softconnect = (is_on != 0); - if (can_pullup(udc)) - fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), - &dr_regs->usbcmd); - else - fsl_writel((fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP), - &dr_regs->usbcmd); - - return 0; -} - -/* defined in gadget.h */ -static struct usb_gadget_ops fsl_gadget_ops = { - .get_frame = fsl_get_frame, - .wakeup = fsl_wakeup, -/* .set_selfpowered = fsl_set_selfpowered, */ /* Always selfpowered */ - .vbus_session = fsl_vbus_session, - .vbus_draw = fsl_vbus_draw, - .pullup = fsl_pullup, -}; - -/* Set protocol stall on ep0, protocol stall will automatically be cleared - on new transaction */ -static void ep0stall(struct fsl_udc *udc) -{ - u32 tmp; - - /* must set tx and rx to stall at the same time */ - tmp = fsl_readl(&dr_regs->endptctrl[0]); - tmp |= EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL; - fsl_writel(tmp, &dr_regs->endptctrl[0]); - udc->ep0_state = WAIT_FOR_SETUP; - udc->ep0_dir = 0; -} - -/* Prime a status phase for ep0 */ -static int ep0_prime_status(struct fsl_udc *udc, int direction) -{ - struct fsl_req *req = udc->status_req; - struct fsl_ep *ep; - - if (direction == EP_DIR_IN) - udc->ep0_dir = USB_DIR_IN; - else - udc->ep0_dir = USB_DIR_OUT; - - ep = &udc->eps[0]; - udc->ep0_state = WAIT_FOR_OUT_STATUS; - - req->ep = ep; - req->req.length = 0; - req->req.status = -EINPROGRESS; - req->req.actual = 0; - req->req.complete = NULL; - req->dtd_count = 0; - - if (fsl_req_to_dtd(req) == 0) - fsl_queue_td(ep, req); - else - return -ENOMEM; - - list_add_tail(&req->queue, &ep->queue); - - return 0; -} - -static void udc_reset_ep_queue(struct fsl_udc *udc, u8 pipe) -{ - struct fsl_ep *ep = get_ep_by_pipe(udc, pipe); - - if (ep->name) - nuke(ep, -ESHUTDOWN); -} - -/* - * ch9 Set address - */ -static void ch9setaddress(struct fsl_udc *udc, u16 value, u16 index, u16 length) -{ - /* Save the new address to device struct */ - udc->device_address = (u8) value; - /* Update usb state */ - udc->usb_state = USB_STATE_ADDRESS; - /* Status phase */ - if (ep0_prime_status(udc, EP_DIR_IN)) - ep0stall(udc); -} - -/* - * ch9 Get status - */ -static void ch9getstatus(struct fsl_udc *udc, u8 request_type, u16 value, - u16 index, u16 length) -{ - u16 tmp = 0; /* Status, cpu endian */ - struct fsl_req *req; - struct fsl_ep *ep; - - ep = &udc->eps[0]; - - if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) { - /* Get device status */ - tmp = 1 << USB_DEVICE_SELF_POWERED; - tmp |= udc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP; - } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) { - /* Get interface status */ - /* We don't have interface information in udc driver */ - tmp = 0; - } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_ENDPOINT) { - /* Get endpoint status */ - struct fsl_ep *target_ep; - - target_ep = get_ep_by_pipe(udc, get_pipe_by_windex(index)); - - /* stall if endpoint doesn't exist */ - if (!target_ep->desc) - goto stall; - tmp = dr_ep_get_stall(ep_index(target_ep), ep_is_in(target_ep)) - << USB_ENDPOINT_HALT; - } - - udc->ep0_dir = USB_DIR_IN; - /* Borrow the per device status_req */ - req = udc->status_req; - /* Fill in the reqest structure */ - *((u16 *) req->req.buf) = cpu_to_le16(tmp); - req->ep = ep; - req->req.length = 2; - req->req.status = -EINPROGRESS; - req->req.actual = 0; - req->req.complete = NULL; - req->dtd_count = 0; - - /* prime the data phase */ - if ((fsl_req_to_dtd(req) == 0)) - fsl_queue_td(ep, req); - else /* no mem */ - goto stall; - - list_add_tail(&req->queue, &ep->queue); - udc->ep0_state = DATA_STATE_XMIT; - return; -stall: - ep0stall(udc); -} - -static void setup_received_irq(struct fsl_udc *udc, - struct usb_ctrlrequest *setup) -{ - u16 wValue = le16_to_cpu(setup->wValue); - u16 wIndex = le16_to_cpu(setup->wIndex); - u16 wLength = le16_to_cpu(setup->wLength); - - udc_reset_ep_queue(udc, 0); - - /* We process some stardard setup requests here */ - switch (setup->bRequest) { - case USB_REQ_GET_STATUS: - /* Data+Status phase from udc */ - if ((setup->bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) - != (USB_DIR_IN | USB_TYPE_STANDARD)) - break; - ch9getstatus(udc, setup->bRequestType, wValue, wIndex, wLength); - return; - - case USB_REQ_SET_ADDRESS: - /* Status phase from udc */ - if (setup->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD - | USB_RECIP_DEVICE)) - break; - ch9setaddress(udc, wValue, wIndex, wLength); - return; - - case USB_REQ_CLEAR_FEATURE: - case USB_REQ_SET_FEATURE: - /* Status phase from udc */ - { - int rc = -EOPNOTSUPP; - - if ((setup->bRequestType & (USB_RECIP_MASK | USB_TYPE_MASK)) - == (USB_RECIP_ENDPOINT | USB_TYPE_STANDARD)) { - int pipe = get_pipe_by_windex(wIndex); - struct fsl_ep *ep; - - if (wValue != 0 || wLength != 0 || pipe > udc->max_ep) - break; - ep = get_ep_by_pipe(udc, pipe); - - spin_unlock(&udc->lock); - rc = fsl_ep_set_halt(&ep->ep, - (setup->bRequest == USB_REQ_SET_FEATURE) - ? 1 : 0); - spin_lock(&udc->lock); - - } else if ((setup->bRequestType & (USB_RECIP_MASK - | USB_TYPE_MASK)) == (USB_RECIP_DEVICE - | USB_TYPE_STANDARD)) { - /* Note: The driver has not include OTG support yet. - * This will be set when OTG support is added */ - if (!gadget_is_otg(&udc->gadget)) - break; - else if (setup->bRequest == USB_DEVICE_B_HNP_ENABLE) - udc->gadget.b_hnp_enable = 1; - else if (setup->bRequest == USB_DEVICE_A_HNP_SUPPORT) - udc->gadget.a_hnp_support = 1; - else if (setup->bRequest == - USB_DEVICE_A_ALT_HNP_SUPPORT) - udc->gadget.a_alt_hnp_support = 1; - else - break; - rc = 0; - } else - break; - - if (rc == 0) { - if (ep0_prime_status(udc, EP_DIR_IN)) - ep0stall(udc); - } - return; - } - - default: - break; - } - - /* Requests handled by gadget */ - if (wLength) { - /* Data phase from gadget, status phase from udc */ - udc->ep0_dir = (setup->bRequestType & USB_DIR_IN) - ? USB_DIR_IN : USB_DIR_OUT; - spin_unlock(&udc->lock); - if (udc->driver->setup(&udc->gadget, - &udc->local_setup_buff) < 0) - ep0stall(udc); - spin_lock(&udc->lock); - udc->ep0_state = (setup->bRequestType & USB_DIR_IN) - ? DATA_STATE_XMIT : DATA_STATE_RECV; - } else { - /* No data phase, IN status from gadget */ - udc->ep0_dir = USB_DIR_IN; - spin_unlock(&udc->lock); - if (udc->driver->setup(&udc->gadget, - &udc->local_setup_buff) < 0) - ep0stall(udc); - spin_lock(&udc->lock); - udc->ep0_state = WAIT_FOR_OUT_STATUS; - } -} - -/* Process request for Data or Status phase of ep0 - * prime status phase if needed */ -static void ep0_req_complete(struct fsl_udc *udc, struct fsl_ep *ep0, - struct fsl_req *req) -{ - if (udc->usb_state == USB_STATE_ADDRESS) { - /* Set the new address */ - u32 new_address = (u32) udc->device_address; - fsl_writel(new_address << USB_DEVICE_ADDRESS_BIT_POS, - &dr_regs->deviceaddr); - } - - done(ep0, req, 0); - - switch (udc->ep0_state) { - case DATA_STATE_XMIT: - /* receive status phase */ - if (ep0_prime_status(udc, EP_DIR_OUT)) - ep0stall(udc); - break; - case DATA_STATE_RECV: - /* send status phase */ - if (ep0_prime_status(udc, EP_DIR_IN)) - ep0stall(udc); - break; - case WAIT_FOR_OUT_STATUS: - udc->ep0_state = WAIT_FOR_SETUP; - break; - case WAIT_FOR_SETUP: - ERR("Unexpect ep0 packets\n"); - break; - default: - ep0stall(udc); - break; - } -} - -/* Tripwire mechanism to ensure a setup packet payload is extracted without - * being corrupted by another incoming setup packet */ -static void tripwire_handler(struct fsl_udc *udc, u8 ep_num, u8 *buffer_ptr) -{ - u32 temp; - struct ep_queue_head *qh; - - qh = &udc->ep_qh[ep_num * 2 + EP_DIR_OUT]; - - /* Clear bit in ENDPTSETUPSTAT */ - temp = fsl_readl(&dr_regs->endptsetupstat); - fsl_writel(temp | (1 << ep_num), &dr_regs->endptsetupstat); - - /* while a hazard exists when setup package arrives */ - do { - /* Set Setup Tripwire */ - temp = fsl_readl(&dr_regs->usbcmd); - fsl_writel(temp | USB_CMD_SUTW, &dr_regs->usbcmd); - - /* Copy the setup packet to local buffer */ - memcpy(buffer_ptr, (u8 *) qh->setup_buffer, 8); - } while (!(fsl_readl(&dr_regs->usbcmd) & USB_CMD_SUTW)); - - /* Clear Setup Tripwire */ - temp = fsl_readl(&dr_regs->usbcmd); - fsl_writel(temp & ~USB_CMD_SUTW, &dr_regs->usbcmd); -} - -/* process-ep_req(): free the completed Tds for this req */ -static int process_ep_req(struct fsl_udc *udc, int pipe, - struct fsl_req *curr_req) -{ - struct ep_td_struct *curr_td; - int td_complete, actual, remaining_length, j, tmp; - int status = 0; - int errors = 0; - struct ep_queue_head *curr_qh = &udc->ep_qh[pipe]; - int direction = pipe % 2; - - curr_td = curr_req->head; - td_complete = 0; - actual = curr_req->req.length; - - for (j = 0; j < curr_req->dtd_count; j++) { - remaining_length = (le32_to_cpu(curr_td->size_ioc_sts) - & DTD_PACKET_SIZE) - >> DTD_LENGTH_BIT_POS; - actual -= remaining_length; - - if ((errors = le32_to_cpu(curr_td->size_ioc_sts) & - DTD_ERROR_MASK)) { - if (errors & DTD_STATUS_HALTED) { - ERR("dTD error %08x QH=%d\n", errors, pipe); - /* Clear the errors and Halt condition */ - tmp = le32_to_cpu(curr_qh->size_ioc_int_sts); - tmp &= ~errors; - curr_qh->size_ioc_int_sts = cpu_to_le32(tmp); - status = -EPIPE; - /* FIXME: continue with next queued TD? */ - - break; - } - if (errors & DTD_STATUS_DATA_BUFF_ERR) { - VDBG("Transfer overflow"); - status = -EPROTO; - break; - } else if (errors & DTD_STATUS_TRANSACTION_ERR) { - VDBG("ISO error"); - status = -EILSEQ; - break; - } else - ERR("Unknown error has occured (0x%x)!\n", - errors); - - } else if (le32_to_cpu(curr_td->size_ioc_sts) - & DTD_STATUS_ACTIVE) { - VDBG("Request not complete"); - status = REQ_UNCOMPLETE; - return status; - } else if (remaining_length) { - if (direction) { - VDBG("Transmit dTD remaining length not zero"); - status = -EPROTO; - break; - } else { - td_complete++; - break; - } - } else { - td_complete++; - VDBG("dTD transmitted successful"); - } - - if (j != curr_req->dtd_count - 1) - curr_td = (struct ep_td_struct *)curr_td->next_td_virt; - } - - if (status) - return status; - - curr_req->req.actual = actual; - - return 0; -} - -/* Process a DTD completion interrupt */ -static void dtd_complete_irq(struct fsl_udc *udc) -{ - u32 bit_pos; - int i, ep_num, direction, bit_mask, status; - struct fsl_ep *curr_ep; - struct fsl_req *curr_req, *temp_req; - - /* Clear the bits in the register */ - bit_pos = fsl_readl(&dr_regs->endptcomplete); - fsl_writel(bit_pos, &dr_regs->endptcomplete); - - if (!bit_pos) - return; - - for (i = 0; i < udc->max_ep * 2; i++) { - ep_num = i >> 1; - direction = i % 2; - - bit_mask = 1 << (ep_num + 16 * direction); - - if (!(bit_pos & bit_mask)) - continue; - - curr_ep = get_ep_by_pipe(udc, i); - - /* If the ep is configured */ - if (curr_ep->name == NULL) { - WARNING("Invalid EP?"); - continue; - } - - /* process the req queue until an uncomplete request */ - list_for_each_entry_safe(curr_req, temp_req, &curr_ep->queue, - queue) { - status = process_ep_req(udc, i, curr_req); - - VDBG("status of process_ep_req= %d, ep = %d", - status, ep_num); - if (status == REQ_UNCOMPLETE) - break; - /* write back status to req */ - curr_req->req.status = status; - - if (ep_num == 0) { - ep0_req_complete(udc, curr_ep, curr_req); - break; - } else - done(curr_ep, curr_req, status); - } - } -} - -/* Process a port change interrupt */ -static void port_change_irq(struct fsl_udc *udc) -{ - u32 speed; - - /* Bus resetting is finished */ - if (!(fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET)) { - /* Get the speed */ - speed = (fsl_readl(&dr_regs->portsc1) - & PORTSCX_PORT_SPEED_MASK); - switch (speed) { - case PORTSCX_PORT_SPEED_HIGH: - udc->gadget.speed = USB_SPEED_HIGH; - break; - case PORTSCX_PORT_SPEED_FULL: - udc->gadget.speed = USB_SPEED_FULL; - break; - case PORTSCX_PORT_SPEED_LOW: - udc->gadget.speed = USB_SPEED_LOW; - break; - default: - udc->gadget.speed = USB_SPEED_UNKNOWN; - break; - } - } - - /* Update USB state */ - if (!udc->resume_state) - udc->usb_state = USB_STATE_DEFAULT; -} - -/* Process suspend interrupt */ -static void suspend_irq(struct fsl_udc *udc) -{ - udc->resume_state = udc->usb_state; - udc->usb_state = USB_STATE_SUSPENDED; - - /* report suspend to the driver, serial.c does not support this */ - if (udc->driver->suspend) - udc->driver->suspend(&udc->gadget); -} - -static void bus_resume(struct fsl_udc *udc) -{ - udc->usb_state = udc->resume_state; - udc->resume_state = 0; - - /* report resume to the driver, serial.c does not support this */ - if (udc->driver->resume) - udc->driver->resume(&udc->gadget); -} - -/* Clear up all ep queues */ -static int reset_queues(struct fsl_udc *udc) -{ - u8 pipe; - - for (pipe = 0; pipe < udc->max_pipes; pipe++) - udc_reset_ep_queue(udc, pipe); - - /* report disconnect; the driver is already quiesced */ - spin_unlock(&udc->lock); - udc->driver->disconnect(&udc->gadget); - spin_lock(&udc->lock); - - return 0; -} - -/* Process reset interrupt */ -static void reset_irq(struct fsl_udc *udc) -{ - u32 temp; - unsigned long timeout; - - /* Clear the device address */ - temp = fsl_readl(&dr_regs->deviceaddr); - fsl_writel(temp & ~USB_DEVICE_ADDRESS_MASK, &dr_regs->deviceaddr); - - udc->device_address = 0; - - /* Clear usb state */ - udc->resume_state = 0; - udc->ep0_dir = 0; - udc->ep0_state = WAIT_FOR_SETUP; - udc->remote_wakeup = 0; /* default to 0 on reset */ - udc->gadget.b_hnp_enable = 0; - udc->gadget.a_hnp_support = 0; - udc->gadget.a_alt_hnp_support = 0; - - /* Clear all the setup token semaphores */ - temp = fsl_readl(&dr_regs->endptsetupstat); - fsl_writel(temp, &dr_regs->endptsetupstat); - - /* Clear all the endpoint complete status bits */ - temp = fsl_readl(&dr_regs->endptcomplete); - fsl_writel(temp, &dr_regs->endptcomplete); - - timeout = jiffies + 100; - while (fsl_readl(&dr_regs->endpointprime)) { - /* Wait until all endptprime bits cleared */ - if (time_after(jiffies, timeout)) { - ERR("Timeout for reset\n"); - break; - } - cpu_relax(); - } - - /* Write 1s to the flush register */ - fsl_writel(0xffffffff, &dr_regs->endptflush); - - if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) { - VDBG("Bus reset"); - /* Reset all the queues, include XD, dTD, EP queue - * head and TR Queue */ - reset_queues(udc); - udc->usb_state = USB_STATE_DEFAULT; - } else { - VDBG("Controller reset"); - /* initialize usb hw reg except for regs for EP, not - * touch usbintr reg */ - dr_controller_setup(udc); - - /* Reset all internal used Queues */ - reset_queues(udc); - - ep0_setup(udc); - - /* Enable DR IRQ reg, Set Run bit, change udc state */ - dr_controller_run(udc); - udc->usb_state = USB_STATE_ATTACHED; - } -} - -/* - * USB device controller interrupt handler - */ -static irqreturn_t fsl_udc_irq(int irq, void *_udc) -{ - struct fsl_udc *udc = _udc; - u32 irq_src; - irqreturn_t status = IRQ_NONE; - unsigned long flags; - - /* Disable ISR for OTG host mode */ - if (udc->stopped) - return IRQ_NONE; - spin_lock_irqsave(&udc->lock, flags); - irq_src = fsl_readl(&dr_regs->usbsts) & fsl_readl(&dr_regs->usbintr); - /* Clear notification bits */ - fsl_writel(irq_src, &dr_regs->usbsts); - - /* VDBG("irq_src [0x%8x]", irq_src); */ - - /* Need to resume? */ - if (udc->usb_state == USB_STATE_SUSPENDED) - if ((fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_SUSPEND) == 0) - bus_resume(udc); - - /* USB Interrupt */ - if (irq_src & USB_STS_INT) { - VDBG("Packet int"); - /* Setup package, we only support ep0 as control ep */ - if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) { - tripwire_handler(udc, 0, - (u8 *) (&udc->local_setup_buff)); - setup_received_irq(udc, &udc->local_setup_buff); - status = IRQ_HANDLED; - } - - /* completion of dtd */ - if (fsl_readl(&dr_regs->endptcomplete)) { - dtd_complete_irq(udc); - status = IRQ_HANDLED; - } - } - - /* SOF (for ISO transfer) */ - if (irq_src & USB_STS_SOF) { - status = IRQ_HANDLED; - } - - /* Port Change */ - if (irq_src & USB_STS_PORT_CHANGE) { - port_change_irq(udc); - status = IRQ_HANDLED; - } - - /* Reset Received */ - if (irq_src & USB_STS_RESET) { - reset_irq(udc); - status = IRQ_HANDLED; - } - - /* Sleep Enable (Suspend) */ - if (irq_src & USB_STS_SUSPEND) { - suspend_irq(udc); - status = IRQ_HANDLED; - } - - if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { - VDBG("Error IRQ %x", irq_src); - } - - spin_unlock_irqrestore(&udc->lock, flags); - return status; -} - -/*----------------------------------------------------------------* - * Hook to gadget drivers - * Called by initialization code of gadget drivers -*----------------------------------------------------------------*/ -int usb_gadget_register_driver(struct usb_gadget_driver *driver) -{ - int retval = -ENODEV; - unsigned long flags = 0; - - if (!udc_controller) - return -ENODEV; - - if (!driver || (driver->speed != USB_SPEED_FULL - && driver->speed != USB_SPEED_HIGH) - || !driver->bind || !driver->disconnect - || !driver->setup) - return -EINVAL; - - if (udc_controller->driver) - return -EBUSY; - - /* lock is needed but whether should use this lock or another */ - spin_lock_irqsave(&udc_controller->lock, flags); - - driver->driver.bus = NULL; - /* hook up the driver */ - udc_controller->driver = driver; - udc_controller->gadget.dev.driver = &driver->driver; - spin_unlock_irqrestore(&udc_controller->lock, flags); - - /* bind udc driver to gadget driver */ - retval = driver->bind(&udc_controller->gadget); - if (retval) { - VDBG("bind to %s --> %d", driver->driver.name, retval); - udc_controller->gadget.dev.driver = NULL; - udc_controller->driver = NULL; - goto out; - } - - /* Enable DR IRQ reg and Set usbcmd reg Run bit */ - dr_controller_run(udc_controller); - udc_controller->usb_state = USB_STATE_ATTACHED; - udc_controller->ep0_state = WAIT_FOR_SETUP; - udc_controller->ep0_dir = 0; - printk(KERN_INFO "%s: bind to driver %s\n", - udc_controller->gadget.name, driver->driver.name); - -out: - if (retval) - printk(KERN_WARNING "gadget driver register failed %d\n", - retval); - return retval; -} -EXPORT_SYMBOL(usb_gadget_register_driver); - -/* Disconnect from gadget driver */ -int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) -{ - struct fsl_ep *loop_ep; - unsigned long flags; - - if (!udc_controller) - return -ENODEV; - - if (!driver || driver != udc_controller->driver || !driver->unbind) - return -EINVAL; - - if (udc_controller->transceiver) - otg_set_peripheral(udc_controller->transceiver, NULL); - - /* stop DR, disable intr */ - dr_controller_stop(udc_controller); - - /* in fact, no needed */ - udc_controller->usb_state = USB_STATE_ATTACHED; - udc_controller->ep0_state = WAIT_FOR_SETUP; - udc_controller->ep0_dir = 0; - - /* stand operation */ - spin_lock_irqsave(&udc_controller->lock, flags); - udc_controller->gadget.speed = USB_SPEED_UNKNOWN; - nuke(&udc_controller->eps[0], -ESHUTDOWN); - list_for_each_entry(loop_ep, &udc_controller->gadget.ep_list, - ep.ep_list) - nuke(loop_ep, -ESHUTDOWN); - spin_unlock_irqrestore(&udc_controller->lock, flags); - - /* report disconnect; the controller is already quiesced */ - driver->disconnect(&udc_controller->gadget); - - /* unbind gadget and unhook driver. */ - driver->unbind(&udc_controller->gadget); - udc_controller->gadget.dev.driver = NULL; - udc_controller->driver = NULL; - - printk(KERN_WARNING "unregistered gadget driver '%s'\n", - driver->driver.name); - return 0; -} -EXPORT_SYMBOL(usb_gadget_unregister_driver); - -/*------------------------------------------------------------------------- - PROC File System Support --------------------------------------------------------------------------*/ -#ifdef CONFIG_USB_GADGET_DEBUG_FILES - -#include <linux/seq_file.h> - -static const char proc_filename[] = "driver/fsl_usb2_udc"; - -static int fsl_proc_read(char *page, char **start, off_t off, int count, - int *eof, void *_dev) -{ - char *buf = page; - char *next = buf; - unsigned size = count; - unsigned long flags; - int t, i; - u32 tmp_reg; - struct fsl_ep *ep = NULL; - struct fsl_req *req; - - struct fsl_udc *udc = udc_controller; - if (off != 0) - return 0; - - spin_lock_irqsave(&udc->lock, flags); - - /* ------basic driver information ---- */ - t = scnprintf(next, size, - DRIVER_DESC "\n" - "%s version: %s\n" - "Gadget driver: %s\n\n", - driver_name, DRIVER_VERSION, - udc->driver ? udc->driver->driver.name : "(none)"); - size -= t; - next += t; - - /* ------ DR Registers ----- */ - tmp_reg = fsl_readl(&dr_regs->usbcmd); - t = scnprintf(next, size, - "USBCMD reg:\n" - "SetupTW: %d\n" - "Run/Stop: %s\n\n", - (tmp_reg & USB_CMD_SUTW) ? 1 : 0, - (tmp_reg & USB_CMD_RUN_STOP) ? "Run" : "Stop"); - size -= t; - next += t; - - tmp_reg = fsl_readl(&dr_regs->usbsts); - t = scnprintf(next, size, - "USB Status Reg:\n" - "Dr Suspend: %d Reset Received: %d System Error: %s " - "USB Error Interrupt: %s\n\n", - (tmp_reg & USB_STS_SUSPEND) ? 1 : 0, - (tmp_reg & USB_STS_RESET) ? 1 : 0, - (tmp_reg & USB_STS_SYS_ERR) ? "Err" : "Normal", - (tmp_reg & USB_STS_ERR) ? "Err detected" : "No err"); - size -= t; - next += t; - - tmp_reg = fsl_readl(&dr_regs->usbintr); - t = scnprintf(next, size, - "USB Intrrupt Enable Reg:\n" - "Sleep Enable: %d SOF Received Enable: %d " - "Reset Enable: %d\n" - "System Error Enable: %d " - "Port Change Dectected Enable: %d\n" - "USB Error Intr Enable: %d USB Intr Enable: %d\n\n", - (tmp_reg & USB_INTR_DEVICE_SUSPEND) ? 1 : 0, - (tmp_reg & USB_INTR_SOF_EN) ? 1 : 0, - (tmp_reg & USB_INTR_RESET_EN) ? 1 : 0, - (tmp_reg & USB_INTR_SYS_ERR_EN) ? 1 : 0, - (tmp_reg & USB_INTR_PTC_DETECT_EN) ? 1 : 0, - (tmp_reg & USB_INTR_ERR_INT_EN) ? 1 : 0, - (tmp_reg & USB_INTR_INT_EN) ? 1 : 0); - size -= t; - next += t; - - tmp_reg = fsl_readl(&dr_regs->frindex); - t = scnprintf(next, size, - "USB Frame Index Reg: Frame Number is 0x%x\n\n", - (tmp_reg & USB_FRINDEX_MASKS)); - size -= t; - next += t; - - tmp_reg = fsl_readl(&dr_regs->deviceaddr); - t = scnprintf(next, size, - "USB Device Address Reg: Device Addr is 0x%x\n\n", - (tmp_reg & USB_DEVICE_ADDRESS_MASK)); - size -= t; - next += t; - - tmp_reg = fsl_readl(&dr_regs->endpointlistaddr); - t = scnprintf(next, size, - "USB Endpoint List Address Reg: " - "Device Addr is 0x%x\n\n", - (tmp_reg & USB_EP_LIST_ADDRESS_MASK)); - size -= t; - next += t; - - tmp_reg = fsl_readl(&dr_regs->portsc1); - t = scnprintf(next, size, - "USB Port Status&Control Reg:\n" - "Port Transceiver Type : %s Port Speed: %s\n" - "PHY Low Power Suspend: %s Port Reset: %s " - "Port Suspend Mode: %s\n" - "Over-current Change: %s " - "Port Enable/Disable Change: %s\n" - "Port Enabled/Disabled: %s " - "Current Connect Status: %s\n\n", ( { - char *s; - switch (tmp_reg & PORTSCX_PTS_FSLS) { - case PORTSCX_PTS_UTMI: - s = "UTMI"; break; - case PORTSCX_PTS_ULPI: - s = "ULPI "; break; - case PORTSCX_PTS_FSLS: - s = "FS/LS Serial"; break; - default: - s = "None"; break; - } - s;} ), ( { - char *s; - switch (tmp_reg & PORTSCX_PORT_SPEED_UNDEF) { - case PORTSCX_PORT_SPEED_FULL: - s = "Full Speed"; break; - case PORTSCX_PORT_SPEED_LOW: - s = "Low Speed"; break; - case PORTSCX_PORT_SPEED_HIGH: - s = "High Speed"; break; - default: - s = "Undefined"; break; - } - s; - } ), - (tmp_reg & PORTSCX_PHY_LOW_POWER_SPD) ? - "Normal PHY mode" : "Low power mode", - (tmp_reg & PORTSCX_PORT_RESET) ? "In Reset" : - "Not in Reset", - (tmp_reg & PORTSCX_PORT_SUSPEND) ? "In " : "Not in", - (tmp_reg & PORTSCX_OVER_CURRENT_CHG) ? "Dected" : - "No", - (tmp_reg & PORTSCX_PORT_EN_DIS_CHANGE) ? "Disable" : - "Not change", - (tmp_reg & PORTSCX_PORT_ENABLE) ? "Enable" : - "Not correct", - (tmp_reg & PORTSCX_CURRENT_CONNECT_STATUS) ? - "Attached" : "Not-Att"); - size -= t; - next += t; - - tmp_reg = fsl_readl(&dr_regs->usbmode); - t = scnprintf(next, size, - "USB Mode Reg: Controller Mode is: %s\n\n", ( { - char *s; - switch (tmp_reg & USB_MODE_CTRL_MODE_HOST) { - case USB_MODE_CTRL_MODE_IDLE: - s = "Idle"; break; - case USB_MODE_CTRL_MODE_DEVICE: - s = "Device Controller"; break; - case USB_MODE_CTRL_MODE_HOST: - s = "Host Controller"; break; - default: - s = "None"; break; - } - s; - } )); - size -= t; - next += t; - - tmp_reg = fsl_readl(&dr_regs->endptsetupstat); - t = scnprintf(next, size, - "Endpoint Setup Status Reg: SETUP on ep 0x%x\n\n", - (tmp_reg & EP_SETUP_STATUS_MASK)); - size -= t; - next += t; - - for (i = 0; i < udc->max_ep / 2; i++) { - tmp_reg = fsl_readl(&dr_regs->endptctrl[i]); - t = scnprintf(next, size, "EP Ctrl Reg [0x%x]: = [0x%x]\n", - i, tmp_reg); - size -= t; - next += t; - } - tmp_reg = fsl_readl(&dr_regs->endpointprime); - t = scnprintf(next, size, "EP Prime Reg = [0x%x]\n\n", tmp_reg); - size -= t; - next += t; - - tmp_reg = usb_sys_regs->snoop1; - t = scnprintf(next, size, "Snoop1 Reg : = [0x%x]\n\n", tmp_reg); - size -= t; - next += t; - - tmp_reg = usb_sys_regs->control; - t = scnprintf(next, size, "General Control Reg : = [0x%x]\n\n", - tmp_reg); - size -= t; - next += t; - - /* ------fsl_udc, fsl_ep, fsl_request structure information ----- */ - ep = &udc->eps[0]; - t = scnprintf(next, size, "For %s Maxpkt is 0x%x index is 0x%x\n", - ep->ep.name, ep_maxpacket(ep), ep_index(ep)); - size -= t; - next += t; - - if (list_empty(&ep->queue)) { - t = scnprintf(next, size, "its req queue is empty\n\n"); - size -= t; - next += t; - } else { - list_for_each_entry(req, &ep->queue, queue) { - t = scnprintf(next, size, - "req %p actual 0x%x length 0x%x buf %p\n", - &req->req, req->req.actual, - req->req.length, req->req.buf); - size -= t; - next += t; - } - } - /* other gadget->eplist ep */ - list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { - if (ep->desc) { - t = scnprintf(next, size, - "\nFor %s Maxpkt is 0x%x " - "index is 0x%x\n", - ep->ep.name, ep_maxpacket(ep), - ep_index(ep)); - size -= t; - next += t; - - if (list_empty(&ep->queue)) { - t = scnprintf(next, size, - "its req queue is empty\n\n"); - size -= t; - next += t; - } else { - list_for_each_entry(req, &ep->queue, queue) { - t = scnprintf(next, size, - "req %p actual 0x%x length " - "0x%x buf %p\n", - &req->req, req->req.actual, - req->req.length, req->req.buf); - size -= t; - next += t; - } /* end for each_entry of ep req */ - } /* end for else */ - } /* end for if(ep->queue) */ - } /* end (ep->desc) */ - - spin_unlock_irqrestore(&udc->lock, flags); - - *eof = 1; - return count - size; -} - -#define create_proc_file() create_proc_read_entry(proc_filename, \ - 0, NULL, fsl_proc_read, NULL) - -#define remove_proc_file() remove_proc_entry(proc_filename, NULL) - -#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ - -#define create_proc_file() do {} while (0) -#define remove_proc_file() do {} while (0) - -#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ - -/*-------------------------------------------------------------------------*/ - -/* Release udc structures */ -static void fsl_udc_release(struct device *dev) -{ - complete(udc_controller->done); - dma_free_coherent(dev, udc_controller->ep_qh_size, - udc_controller->ep_qh, udc_controller->ep_qh_dma); - kfree(udc_controller); -} - -/****************************************************************** - Internal structure setup functions -*******************************************************************/ -/*------------------------------------------------------------------ - * init resource for globle controller - * Return the udc handle on success or NULL on failure - ------------------------------------------------------------------*/ -static int __init struct_udc_setup(struct fsl_udc *udc, - struct platform_device *pdev) -{ - struct fsl_usb2_platform_data *pdata; - size_t size; - - pdata = pdev->dev.platform_data; - udc->phy_mode = pdata->phy_mode; - - udc->eps = kzalloc(sizeof(struct fsl_ep) * udc->max_ep, GFP_KERNEL); - if (!udc->eps) { - ERR("malloc fsl_ep failed\n"); - return -1; - } - - /* initialized QHs, take care of alignment */ - size = udc->max_ep * sizeof(struct ep_queue_head); - if (size < QH_ALIGNMENT) - size = QH_ALIGNMENT; - else if ((size % QH_ALIGNMENT) != 0) { - size += QH_ALIGNMENT + 1; - size &= ~(QH_ALIGNMENT - 1); - } - udc->ep_qh = dma_alloc_coherent(&pdev->dev, size, - &udc->ep_qh_dma, GFP_KERNEL); - if (!udc->ep_qh) { - ERR("malloc QHs for udc failed\n"); - kfree(udc->eps); - return -1; - } - - udc->ep_qh_size = size; - - /* Initialize ep0 status request structure */ - /* FIXME: fsl_alloc_request() ignores ep argument */ - udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), - struct fsl_req, req); - /* allocate a small amount of memory to get valid address */ - udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); - udc->status_req->req.dma = virt_to_phys(udc->status_req->req.buf); - - udc->resume_state = USB_STATE_NOTATTACHED; - udc->usb_state = USB_STATE_POWERED; - udc->ep0_dir = 0; - udc->remote_wakeup = 0; /* default to 0 on reset */ - - return 0; -} - -/*---------------------------------------------------------------- - * Setup the fsl_ep struct for eps - * Link fsl_ep->ep to gadget->ep_list - * ep0out is not used so do nothing here - * ep0in should be taken care - *--------------------------------------------------------------*/ -static int __init struct_ep_setup(struct fsl_udc *udc, unsigned char index, - char *name, int link) -{ - struct fsl_ep *ep = &udc->eps[index]; - - ep->udc = udc; - strcpy(ep->name, name); - ep->ep.name = ep->name; - - ep->ep.ops = &fsl_ep_ops; - ep->stopped = 0; - - /* for ep0: maxP defined in desc - * for other eps, maxP is set by epautoconfig() called by gadget layer - */ - ep->ep.maxpacket = (unsigned short) ~0; - - /* the queue lists any req for this ep */ - INIT_LIST_HEAD(&ep->queue); - - /* gagdet.ep_list used for ep_autoconfig so no ep0 */ - if (link) - list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); - ep->gadget = &udc->gadget; - ep->qh = &udc->ep_qh[index]; - - return 0; -} - -/* Driver probe function - * all intialization operations implemented here except enabling usb_intr reg - * board setup should have been done in the platform code - */ -static int __init fsl_udc_probe(struct platform_device *pdev) -{ - struct resource *res; - int ret = -ENODEV; - unsigned int i; - u32 dccparams; - - if (strcmp(pdev->name, driver_name)) { - VDBG("Wrong device"); - return -ENODEV; - } - - udc_controller = kzalloc(sizeof(struct fsl_udc), GFP_KERNEL); - if (udc_controller == NULL) { - ERR("malloc udc failed\n"); - return -ENOMEM; - } - - spin_lock_init(&udc_controller->lock); - udc_controller->stopped = 1; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - ret = -ENXIO; - goto err_kfree; - } - - if (!request_mem_region(res->start, res->end - res->start + 1, - driver_name)) { - ERR("request mem region for %s failed\n", pdev->name); - ret = -EBUSY; - goto err_kfree; - } - - dr_regs = ioremap(res->start, res->end - res->start + 1); - if (!dr_regs) { - ret = -ENOMEM; - goto err_release_mem_region; - } - - usb_sys_regs = (struct usb_sys_interface *) - ((u32)dr_regs + USB_DR_SYS_OFFSET); - - /* Read Device Controller Capability Parameters register */ - dccparams = fsl_readl(&dr_regs->dccparams); - if (!(dccparams & DCCPARAMS_DC)) { - ERR("This SOC doesn't support device role\n"); - ret = -ENODEV; - goto err_iounmap; - } - /* Get max device endpoints */ - /* DEN is bidirectional ep number, max_ep doubles the number */ - udc_controller->max_ep = (dccparams & DCCPARAMS_DEN_MASK) * 2; - - udc_controller->irq = platform_get_irq(pdev, 0); - if (!udc_controller->irq) { - ret = -ENODEV; - goto err_iounmap; - } - - ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED, - driver_name, udc_controller); - if (ret != 0) { - ERR("cannot request irq %d err %d\n", - udc_controller->irq, ret); - goto err_iounmap; - } - - /* Initialize the udc structure including QH member and other member */ - if (struct_udc_setup(udc_controller, pdev)) { - ERR("Can't initialize udc data structure\n"); - ret = -ENOMEM; - goto err_free_irq; - } - - /* initialize usb hw reg except for regs for EP, - * leave usbintr reg untouched */ - dr_controller_setup(udc_controller); - - /* Setup gadget structure */ - udc_controller->gadget.ops = &fsl_gadget_ops; - udc_controller->gadget.is_dualspeed = 1; - udc_controller->gadget.ep0 = &udc_controller->eps[0].ep; - INIT_LIST_HEAD(&udc_controller->gadget.ep_list); - udc_controller->gadget.speed = USB_SPEED_UNKNOWN; - udc_controller->gadget.name = driver_name; - - /* Setup gadget.dev and register with kernel */ - dev_set_name(&udc_controller->gadget.dev, "gadget"); - udc_controller->gadget.dev.release = fsl_udc_release; - udc_controller->gadget.dev.parent = &pdev->dev; - ret = device_register(&udc_controller->gadget.dev); - if (ret < 0) - goto err_free_irq; - - /* setup QH and epctrl for ep0 */ - ep0_setup(udc_controller); - - /* setup udc->eps[] for ep0 */ - struct_ep_setup(udc_controller, 0, "ep0", 0); - /* for ep0: the desc defined here; - * for other eps, gadget layer called ep_enable with defined desc - */ - udc_controller->eps[0].desc = &fsl_ep0_desc; - udc_controller->eps[0].ep.maxpacket = USB_MAX_CTRL_PAYLOAD; - - /* setup the udc->eps[] for non-control endpoints and link - * to gadget.ep_list */ - for (i = 1; i < (int)(udc_controller->max_ep / 2); i++) { - char name[14]; - - sprintf(name, "ep%dout", i); - struct_ep_setup(udc_controller, i * 2, name, 1); - sprintf(name, "ep%din", i); - struct_ep_setup(udc_controller, i * 2 + 1, name, 1); - } - - /* use dma_pool for TD management */ - udc_controller->td_pool = dma_pool_create("udc_td", &pdev->dev, - sizeof(struct ep_td_struct), - DTD_ALIGNMENT, UDC_DMA_BOUNDARY); - if (udc_controller->td_pool == NULL) { - ret = -ENOMEM; - goto err_unregister; - } - create_proc_file(); - return 0; - -err_unregister: - device_unregister(&udc_controller->gadget.dev); -err_free_irq: - free_irq(udc_controller->irq, udc_controller); -err_iounmap: - iounmap(dr_regs); -err_release_mem_region: - release_mem_region(res->start, res->end - res->start + 1); -err_kfree: - kfree(udc_controller); - udc_controller = NULL; - return ret; -} - -/* Driver removal function - * Free resources and finish pending transactions - */ -static int __exit fsl_udc_remove(struct platform_device *pdev) -{ - struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - DECLARE_COMPLETION(done); - - if (!udc_controller) - return -ENODEV; - udc_controller->done = &done; - - /* DR has been stopped in usb_gadget_unregister_driver() */ - remove_proc_file(); - - /* Free allocated memory */ - kfree(udc_controller->status_req->req.buf); - kfree(udc_controller->status_req); - kfree(udc_controller->eps); - - dma_pool_destroy(udc_controller->td_pool); - free_irq(udc_controller->irq, udc_controller); - iounmap(dr_regs); - release_mem_region(res->start, res->end - res->start + 1); - - device_unregister(&udc_controller->gadget.dev); - /* free udc --wait for the release() finished */ - wait_for_completion(&done); - - return 0; -} - -/*----------------------------------------------------------------- - * Modify Power management attributes - * Used by OTG statemachine to disable gadget temporarily - -----------------------------------------------------------------*/ -static int fsl_udc_suspend(struct platform_device *pdev, pm_message_t state) -{ - dr_controller_stop(udc_controller); - return 0; -} - -/*----------------------------------------------------------------- - * Invoked on USB resume. May be called in_interrupt. - * Here we start the DR controller and enable the irq - *-----------------------------------------------------------------*/ -static int fsl_udc_resume(struct platform_device *pdev) -{ - /* Enable DR irq reg and set controller Run */ - if (udc_controller->stopped) { - dr_controller_setup(udc_controller); - dr_controller_run(udc_controller); - } - udc_controller->usb_state = USB_STATE_ATTACHED; - udc_controller->ep0_state = WAIT_FOR_SETUP; - udc_controller->ep0_dir = 0; - return 0; -} - -/*------------------------------------------------------------------------- - Register entry point for the peripheral controller driver ---------------------------------------------------------------------------*/ - -static struct platform_driver udc_driver = { - .remove = __exit_p(fsl_udc_remove), - /* these suspend and resume are not usb suspend and resume */ - .suspend = fsl_udc_suspend, - .resume = fsl_udc_resume, - .driver = { - .name = (char *)driver_name, - .owner = THIS_MODULE, - }, -}; - -static int __init udc_init(void) -{ - printk(KERN_INFO "%s (%s)\n", driver_desc, DRIVER_VERSION); - return platform_driver_probe(&udc_driver, fsl_udc_probe); -} - -module_init(udc_init); - -static void __exit udc_exit(void) -{ - platform_driver_unregister(&udc_driver); - printk(KERN_WARNING "%s unregistered\n", driver_desc); -} - -module_exit(udc_exit); - -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:fsl-usb2-udc"); diff --git a/drivers/usb/gadget/fsl_usb2_udc.h b/drivers/usb/gadget/fsl_usb2_udc.h index e63ef12645f5..20aeceed48c7 100644 --- a/drivers/usb/gadget/fsl_usb2_udc.h +++ b/drivers/usb/gadget/fsl_usb2_udc.h @@ -563,4 +563,22 @@ static void dump_msg(const char *label, const u8 * buf, unsigned int length) * 2 + ((windex & USB_DIR_IN) ? 1 : 0)) #define get_pipe_by_ep(EP) (ep_index(EP) * 2 + ep_is_in(EP)) +struct platform_device; +#ifdef CONFIG_ARCH_MXC +int fsl_udc_clk_init(struct platform_device *pdev); +void fsl_udc_clk_finalize(struct platform_device *pdev); +void fsl_udc_clk_release(void); +#else +static inline int fsl_udc_clk_init(struct platform_device *pdev) +{ + return 0; +} +static inline void fsl_udc_clk_finalize(struct platform_device *pdev) +{ +} +static inline void fsl_udc_clk_release(void) +{ +} +#endif + #endif -- cgit v1.2.3 From 5b7d70c6dbf2db786395cbd21750a1a4ce222f84 Mon Sep 17 00:00:00 2001 From: Ben Dooks <ben@simtec.co.uk> Date: Tue, 2 Jun 2009 14:58:06 +0100 Subject: USB: Gadget driver for Samsung HS/OtG block Driver support for the new high-speed/OtG block that is in the newer line of Samsung SoC devices such as the S3C64XX series. This driver does not currntly have DMA support enabled due to issues with buffer alignment which need to be sorted out. Signed-off-by: Ben Dooks <ben@simtec.co.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- .../arm/plat-s3c/include/plat/regs-usb-hsotg-phy.h | 50 + arch/arm/plat-s3c/include/plat/regs-usb-hsotg.h | 377 +++ drivers/usb/gadget/Kconfig | 14 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/s3c-hsotg.c | 3269 ++++++++++++++++++++ 5 files changed, 3711 insertions(+) create mode 100644 arch/arm/plat-s3c/include/plat/regs-usb-hsotg-phy.h create mode 100644 arch/arm/plat-s3c/include/plat/regs-usb-hsotg.h create mode 100644 drivers/usb/gadget/s3c-hsotg.c (limited to 'drivers') diff --git a/arch/arm/plat-s3c/include/plat/regs-usb-hsotg-phy.h b/arch/arm/plat-s3c/include/plat/regs-usb-hsotg-phy.h new file mode 100644 index 000000000000..36a85f5000c8 --- /dev/null +++ b/arch/arm/plat-s3c/include/plat/regs-usb-hsotg-phy.h @@ -0,0 +1,50 @@ +/* arch/arm/plat-s3c/include/plat/regs-usb-hsotg-phy.h + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * S3C - USB2.0 Highspeed/OtG device PHY registers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +/* Note, this is a seperate header file as some of the clock framework + * needs to touch this if the clk_48m is used as the USB OHCI or other + * peripheral source. +*/ + +#ifndef __PLAT_S3C64XX_REGS_USB_HSOTG_PHY_H +#define __PLAT_S3C64XX_REGS_USB_HSOTG_PHY_H __FILE__ + +/* S3C64XX_PA_USB_HSPHY */ + +#define S3C_HSOTG_PHYREG(x) ((x) + S3C_VA_USB_HSPHY) + +#define S3C_PHYPWR S3C_HSOTG_PHYREG(0x00) +#define SRC_PHYPWR_OTG_DISABLE (1 << 4) +#define SRC_PHYPWR_ANALOG_POWERDOWN (1 << 3) +#define SRC_PHYPWR_FORCE_SUSPEND (1 << 1) + +#define S3C_PHYCLK S3C_HSOTG_PHYREG(0x04) +#define S3C_PHYCLK_MODE_USB11 (1 << 6) +#define S3C_PHYCLK_EXT_OSC (1 << 5) +#define S3C_PHYCLK_CLK_FORCE (1 << 4) +#define S3C_PHYCLK_ID_PULL (1 << 2) +#define S3C_PHYCLK_CLKSEL_MASK (0x3 << 0) +#define S3C_PHYCLK_CLKSEL_SHIFT (0) +#define S3C_PHYCLK_CLKSEL_48M (0x0 << 0) +#define S3C_PHYCLK_CLKSEL_12M (0x2 << 0) +#define S3C_PHYCLK_CLKSEL_24M (0x3 << 0) + +#define S3C_RSTCON S3C_HSOTG_PHYREG(0x08) +#define S3C_RSTCON_PHYCLK (1 << 2) +#define S3C_RSTCON_HCLK (1 << 2) +#define S3C_RSTCON_PHY (1 << 0) + +#define S3C_PHYTUNE S3C_HSOTG_PHYREG(0x20) + +#endif /* __PLAT_S3C64XX_REGS_USB_HSOTG_PHY_H */ diff --git a/arch/arm/plat-s3c/include/plat/regs-usb-hsotg.h b/arch/arm/plat-s3c/include/plat/regs-usb-hsotg.h new file mode 100644 index 000000000000..8d18d9d4d148 --- /dev/null +++ b/arch/arm/plat-s3c/include/plat/regs-usb-hsotg.h @@ -0,0 +1,377 @@ +/* arch/arm/plat-s3c/include/plat/regs-usb-hsotg.h + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * S3C - USB2.0 Highspeed/OtG device block registers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#ifndef __PLAT_S3C64XX_REGS_USB_HSOTG_H +#define __PLAT_S3C64XX_REGS_USB_HSOTG_H __FILE__ + +#define S3C_HSOTG_REG(x) (x) + +#define S3C_GOTGCTL S3C_HSOTG_REG(0x000) +#define S3C_GOTGCTL_BSESVLD (1 << 19) +#define S3C_GOTGCTL_ASESVLD (1 << 18) +#define S3C_GOTGCTL_DBNC_SHORT (1 << 17) +#define S3C_GOTGCTL_CONID_B (1 << 16) +#define S3C_GOTGCTL_DEVHNPEN (1 << 11) +#define S3C_GOTGCTL_HSSETHNPEN (1 << 10) +#define S3C_GOTGCTL_HNPREQ (1 << 9) +#define S3C_GOTGCTL_HSTNEGSCS (1 << 8) +#define S3C_GOTGCTL_SESREQ (1 << 1) +#define S3C_GOTGCTL_SESREQSCS (1 << 0) + +#define S3C_GOTGINT S3C_HSOTG_REG(0x004) +#define S3C_GOTGINT_DbnceDone (1 << 19) +#define S3C_GOTGINT_ADevTOUTChg (1 << 18) +#define S3C_GOTGINT_HstNegDet (1 << 17) +#define S3C_GOTGINT_HstnegSucStsChng (1 << 9) +#define S3C_GOTGINT_SesReqSucStsChng (1 << 8) +#define S3C_GOTGINT_SesEndDet (1 << 2) + +#define S3C_GAHBCFG S3C_HSOTG_REG(0x008) +#define S3C_GAHBCFG_PTxFEmpLvl (1 << 8) +#define S3C_GAHBCFG_NPTxFEmpLvl (1 << 7) +#define S3C_GAHBCFG_DMAEn (1 << 5) +#define S3C_GAHBCFG_HBstLen_MASK (0xf << 1) +#define S3C_GAHBCFG_HBstLen_SHIFT (1) +#define S3C_GAHBCFG_HBstLen_Single (0x0 << 1) +#define S3C_GAHBCFG_HBstLen_Incr (0x1 << 1) +#define S3C_GAHBCFG_HBstLen_Incr4 (0x3 << 1) +#define S3C_GAHBCFG_HBstLen_Incr8 (0x5 << 1) +#define S3C_GAHBCFG_HBstLen_Incr16 (0x7 << 1) +#define S3C_GAHBCFG_GlblIntrEn (1 << 0) + +#define S3C_GUSBCFG S3C_HSOTG_REG(0x00C) +#define S3C_GUSBCFG_PHYLPClkSel (1 << 15) +#define S3C_GUSBCFG_HNPCap (1 << 9) +#define S3C_GUSBCFG_SRPCap (1 << 8) +#define S3C_GUSBCFG_PHYIf16 (1 << 3) +#define S3C_GUSBCFG_TOutCal_MASK (0x7 << 0) +#define S3C_GUSBCFG_TOutCal_SHIFT (0) +#define S3C_GUSBCFG_TOutCal_LIMIT (0x7) +#define S3C_GUSBCFG_TOutCal(_x) ((_x) << 0) + +#define S3C_GRSTCTL S3C_HSOTG_REG(0x010) + +#define S3C_GRSTCTL_AHBIdle (1 << 31) +#define S3C_GRSTCTL_DMAReq (1 << 30) +#define S3C_GRSTCTL_TxFNum_MASK (0x1f << 6) +#define S3C_GRSTCTL_TxFNum_SHIFT (6) +#define S3C_GRSTCTL_TxFNum_LIMIT (0x1f) +#define S3C_GRSTCTL_TxFNum(_x) ((_x) << 6) +#define S3C_GRSTCTL_TxFFlsh (1 << 5) +#define S3C_GRSTCTL_RxFFlsh (1 << 4) +#define S3C_GRSTCTL_INTknQFlsh (1 << 3) +#define S3C_GRSTCTL_FrmCntrRst (1 << 2) +#define S3C_GRSTCTL_HSftRst (1 << 1) +#define S3C_GRSTCTL_CSftRst (1 << 0) + +#define S3C_GINTSTS S3C_HSOTG_REG(0x014) +#define S3C_GINTMSK S3C_HSOTG_REG(0x018) + +#define S3C_GINTSTS_WkUpInt (1 << 31) +#define S3C_GINTSTS_SessReqInt (1 << 30) +#define S3C_GINTSTS_DisconnInt (1 << 29) +#define S3C_GINTSTS_ConIDStsChng (1 << 28) +#define S3C_GINTSTS_PTxFEmp (1 << 26) +#define S3C_GINTSTS_HChInt (1 << 25) +#define S3C_GINTSTS_PrtInt (1 << 24) +#define S3C_GINTSTS_FetSusp (1 << 22) +#define S3C_GINTSTS_incompIP (1 << 21) +#define S3C_GINTSTS_IncomplSOIN (1 << 20) +#define S3C_GINTSTS_OEPInt (1 << 19) +#define S3C_GINTSTS_IEPInt (1 << 18) +#define S3C_GINTSTS_EPMis (1 << 17) +#define S3C_GINTSTS_EOPF (1 << 15) +#define S3C_GINTSTS_ISOutDrop (1 << 14) +#define S3C_GINTSTS_EnumDone (1 << 13) +#define S3C_GINTSTS_USBRst (1 << 12) +#define S3C_GINTSTS_USBSusp (1 << 11) +#define S3C_GINTSTS_ErlySusp (1 << 10) +#define S3C_GINTSTS_GOUTNakEff (1 << 7) +#define S3C_GINTSTS_GINNakEff (1 << 6) +#define S3C_GINTSTS_NPTxFEmp (1 << 5) +#define S3C_GINTSTS_RxFLvl (1 << 4) +#define S3C_GINTSTS_SOF (1 << 3) +#define S3C_GINTSTS_OTGInt (1 << 2) +#define S3C_GINTSTS_ModeMis (1 << 1) +#define S3C_GINTSTS_CurMod_Host (1 << 0) + +#define S3C_GRXSTSR S3C_HSOTG_REG(0x01C) +#define S3C_GRXSTSP S3C_HSOTG_REG(0x020) + +#define S3C_GRXSTS_FN_MASK (0x7f << 25) +#define S3C_GRXSTS_FN_SHIFT (25) + +#define S3C_GRXSTS_PktSts_MASK (0xf << 17) +#define S3C_GRXSTS_PktSts_SHIFT (17) +#define S3C_GRXSTS_PktSts_GlobalOutNAK (0x1 << 17) +#define S3C_GRXSTS_PktSts_OutRX (0x2 << 17) +#define S3C_GRXSTS_PktSts_OutDone (0x3 << 17) +#define S3C_GRXSTS_PktSts_SetupDone (0x4 << 17) +#define S3C_GRXSTS_PktSts_SetupRX (0x6 << 17) + +#define S3C_GRXSTS_DPID_MASK (0x3 << 15) +#define S3C_GRXSTS_DPID_SHIFT (15) +#define S3C_GRXSTS_ByteCnt_MASK (0x7ff << 4) +#define S3C_GRXSTS_ByteCnt_SHIFT (4) +#define S3C_GRXSTS_EPNum_MASK (0xf << 0) +#define S3C_GRXSTS_EPNum_SHIFT (0) + +#define S3C_GRXFSIZ S3C_HSOTG_REG(0x024) + +#define S3C_GNPTXFSIZ S3C_HSOTG_REG(0x028) + +#define S3C_GNPTXFSIZ_NPTxFDep_MASK (0xffff << 16) +#define S3C_GNPTXFSIZ_NPTxFDep_SHIFT (16) +#define S3C_GNPTXFSIZ_NPTxFDep_LIMIT (0xffff) +#define S3C_GNPTXFSIZ_NPTxFDep(_x) ((_x) << 16) +#define S3C_GNPTXFSIZ_NPTxFStAddr_MASK (0xffff << 0) +#define S3C_GNPTXFSIZ_NPTxFStAddr_SHIFT (0) +#define S3C_GNPTXFSIZ_NPTxFStAddr_LIMIT (0xffff) +#define S3C_GNPTXFSIZ_NPTxFStAddr(_x) ((_x) << 0) + +#define S3C_GNPTXSTS S3C_HSOTG_REG(0x02C) + +#define S3C_GNPTXSTS_NPtxQTop_MASK (0x7f << 24) +#define S3C_GNPTXSTS_NPtxQTop_SHIFT (24) + +#define S3C_GNPTXSTS_NPTxQSpcAvail_MASK (0xff << 16) +#define S3C_GNPTXSTS_NPTxQSpcAvail_SHIFT (16) +#define S3C_GNPTXSTS_NPTxQSpcAvail_GET(_v) (((_v) >> 16) & 0xff) + +#define S3C_GNPTXSTS_NPTxFSpcAvail_MASK (0xffff << 0) +#define S3C_GNPTXSTS_NPTxFSpcAvail_SHIFT (0) +#define S3C_GNPTXSTS_NPTxFSpcAvail_GET(_v) (((_v) >> 0) & 0xffff) + + +#define S3C_HPTXFSIZ S3C_HSOTG_REG(0x100) + +#define S3C_DPTXFSIZn(_a) S3C_HSOTG_REG(0x104 + (((_a) - 1) * 4)) + +#define S3C_DPTXFSIZn_DPTxFSize_MASK (0xffff << 16) +#define S3C_DPTXFSIZn_DPTxFSize_SHIFT (16) +#define S3C_DPTXFSIZn_DPTxFSize_GET(_v) (((_v) >> 16) & 0xffff) +#define S3C_DPTXFSIZn_DPTxFSize_LIMIT (0xffff) +#define S3C_DPTXFSIZn_DPTxFSize(_x) ((_x) << 16) + +#define S3C_DPTXFSIZn_DPTxFStAddr_MASK (0xffff << 0) +#define S3C_DPTXFSIZn_DPTxFStAddr_SHIFT (0) + +/* Device mode registers */ +#define S3C_DCFG S3C_HSOTG_REG(0x800) + +#define S3C_DCFG_EPMisCnt_MASK (0x1f << 18) +#define S3C_DCFG_EPMisCnt_SHIFT (18) +#define S3C_DCFG_EPMisCnt_LIMIT (0x1f) +#define S3C_DCFG_EPMisCnt(_x) ((_x) << 18) + +#define S3C_DCFG_PerFrInt_MASK (0x3 << 11) +#define S3C_DCFG_PerFrInt_SHIFT (11) +#define S3C_DCFG_PerFrInt_LIMIT (0x3) +#define S3C_DCFG_PerFrInt(_x) ((_x) << 11) + +#define S3C_DCFG_DevAddr_MASK (0x7f << 4) +#define S3C_DCFG_DevAddr_SHIFT (4) +#define S3C_DCFG_DevAddr_LIMIT (0x7f) +#define S3C_DCFG_DevAddr(_x) ((_x) << 4) + +#define S3C_DCFG_NZStsOUTHShk (1 << 2) + +#define S3C_DCFG_DevSpd_MASK (0x3 << 0) +#define S3C_DCFG_DevSpd_SHIFT (0) +#define S3C_DCFG_DevSpd_HS (0x0 << 0) +#define S3C_DCFG_DevSpd_FS (0x1 << 0) +#define S3C_DCFG_DevSpd_LS (0x2 << 0) +#define S3C_DCFG_DevSpd_FS48 (0x3 << 0) + +#define S3C_DCTL S3C_HSOTG_REG(0x804) + +#define S3C_DCTL_PWROnPrgDone (1 << 11) +#define S3C_DCTL_CGOUTNak (1 << 10) +#define S3C_DCTL_SGOUTNak (1 << 9) +#define S3C_DCTL_CGNPInNAK (1 << 8) +#define S3C_DCTL_SGNPInNAK (1 << 7) +#define S3C_DCTL_TstCtl_MASK (0x7 << 4) +#define S3C_DCTL_TstCtl_SHIFT (4) +#define S3C_DCTL_GOUTNakSts (1 << 3) +#define S3C_DCTL_GNPINNakSts (1 << 2) +#define S3C_DCTL_SftDiscon (1 << 1) +#define S3C_DCTL_RmtWkUpSig (1 << 0) + +#define S3C_DSTS S3C_HSOTG_REG(0x808) + +#define S3C_DSTS_SOFFN_MASK (0x3fff << 8) +#define S3C_DSTS_SOFFN_SHIFT (8) +#define S3C_DSTS_SOFFN_LIMIT (0x3fff) +#define S3C_DSTS_SOFFN(_x) ((_x) << 8) +#define S3C_DSTS_ErraticErr (1 << 3) +#define S3C_DSTS_EnumSpd_MASK (0x3 << 1) +#define S3C_DSTS_EnumSpd_SHIFT (1) +#define S3C_DSTS_EnumSpd_HS (0x0 << 1) +#define S3C_DSTS_EnumSpd_FS (0x1 << 1) +#define S3C_DSTS_EnumSpd_LS (0x2 << 1) +#define S3C_DSTS_EnumSpd_FS48 (0x3 << 1) + +#define S3C_DSTS_SuspSts (1 << 0) + +#define S3C_DIEPMSK S3C_HSOTG_REG(0x810) + +#define S3C_DIEPMSK_INEPNakEffMsk (1 << 6) +#define S3C_DIEPMSK_INTknEPMisMsk (1 << 5) +#define S3C_DIEPMSK_INTknTXFEmpMsk (1 << 4) +#define S3C_DIEPMSK_TimeOUTMsk (1 << 3) +#define S3C_DIEPMSK_AHBErrMsk (1 << 2) +#define S3C_DIEPMSK_EPDisbldMsk (1 << 1) +#define S3C_DIEPMSK_XferComplMsk (1 << 0) + +#define S3C_DOEPMSK S3C_HSOTG_REG(0x814) + +#define S3C_DOEPMSK_Back2BackSetup (1 << 6) +#define S3C_DOEPMSK_OUTTknEPdisMsk (1 << 4) +#define S3C_DOEPMSK_SetupMsk (1 << 3) +#define S3C_DOEPMSK_AHBErrMsk (1 << 2) +#define S3C_DOEPMSK_EPDisbldMsk (1 << 1) +#define S3C_DOEPMSK_XferComplMsk (1 << 0) + +#define S3C_DAINT S3C_HSOTG_REG(0x818) +#define S3C_DAINTMSK S3C_HSOTG_REG(0x81C) + +#define S3C_DAINT_OutEP_SHIFT (16) +#define S3C_DAINT_OutEP(x) (1 << ((x) + 16)) +#define S3C_DAINT_InEP(x) (1 << (x)) + +#define S3C_DTKNQR1 S3C_HSOTG_REG(0x820) +#define S3C_DTKNQR2 S3C_HSOTG_REG(0x824) +#define S3C_DTKNQR3 S3C_HSOTG_REG(0x830) +#define S3C_DTKNQR4 S3C_HSOTG_REG(0x834) + +#define S3C_DVBUSDIS S3C_HSOTG_REG(0x828) +#define S3C_DVBUSPULSE S3C_HSOTG_REG(0x82C) + +#define S3C_DIEPCTL0 S3C_HSOTG_REG(0x900) +#define S3C_DOEPCTL0 S3C_HSOTG_REG(0xB00) +#define S3C_DIEPCTL(_a) S3C_HSOTG_REG(0x900 + ((_a) * 0x20)) +#define S3C_DOEPCTL(_a) S3C_HSOTG_REG(0xB00 + ((_a) * 0x20)) + +/* EP0 specialness: + * bits[29..28] - reserved (no SetD0PID, SetD1PID) + * bits[25..22] - should always be zero, this isn't a periodic endpoint + * bits[10..0] - MPS setting differenct for EP0 +*/ +#define S3C_D0EPCTL_MPS_MASK (0x3 << 0) +#define S3C_D0EPCTL_MPS_SHIFT (0) +#define S3C_D0EPCTL_MPS_64 (0x0 << 0) +#define S3C_D0EPCTL_MPS_32 (0x1 << 0) +#define S3C_D0EPCTL_MPS_16 (0x2 << 0) +#define S3C_D0EPCTL_MPS_8 (0x3 << 0) + +#define S3C_DxEPCTL_EPEna (1 << 31) +#define S3C_DxEPCTL_EPDis (1 << 30) +#define S3C_DxEPCTL_SetD1PID (1 << 29) +#define S3C_DxEPCTL_SetOddFr (1 << 29) +#define S3C_DxEPCTL_SetD0PID (1 << 28) +#define S3C_DxEPCTL_SetEvenFr (1 << 28) +#define S3C_DxEPCTL_SNAK (1 << 27) +#define S3C_DxEPCTL_CNAK (1 << 26) +#define S3C_DxEPCTL_TxFNum_MASK (0xf << 22) +#define S3C_DxEPCTL_TxFNum_SHIFT (22) +#define S3C_DxEPCTL_TxFNum_LIMIT (0xf) +#define S3C_DxEPCTL_TxFNum(_x) ((_x) << 22) + +#define S3C_DxEPCTL_Stall (1 << 21) +#define S3C_DxEPCTL_Snp (1 << 20) +#define S3C_DxEPCTL_EPType_MASK (0x3 << 18) +#define S3C_DxEPCTL_EPType_SHIFT (18) +#define S3C_DxEPCTL_EPType_Control (0x0 << 18) +#define S3C_DxEPCTL_EPType_Iso (0x1 << 18) +#define S3C_DxEPCTL_EPType_Bulk (0x2 << 18) +#define S3C_DxEPCTL_EPType_Intterupt (0x3 << 18) + +#define S3C_DxEPCTL_NAKsts (1 << 17) +#define S3C_DxEPCTL_DPID (1 << 16) +#define S3C_DxEPCTL_EOFrNum (1 << 16) +#define S3C_DxEPCTL_USBActEp (1 << 15) +#define S3C_DxEPCTL_NextEp_MASK (0xf << 11) +#define S3C_DxEPCTL_NextEp_SHIFT (11) +#define S3C_DxEPCTL_NextEp_LIMIT (0xf) +#define S3C_DxEPCTL_NextEp(_x) ((_x) << 11) + +#define S3C_DxEPCTL_MPS_MASK (0x7ff << 0) +#define S3C_DxEPCTL_MPS_SHIFT (0) +#define S3C_DxEPCTL_MPS_LIMIT (0x7ff) +#define S3C_DxEPCTL_MPS(_x) ((_x) << 0) + +#define S3C_DIEPINT(_a) S3C_HSOTG_REG(0x908 + ((_a) * 0x20)) +#define S3C_DOEPINT(_a) S3C_HSOTG_REG(0xB08 + ((_a) * 0x20)) + +#define S3C_DxEPINT_INEPNakEff (1 << 6) +#define S3C_DxEPINT_Back2BackSetup (1 << 6) +#define S3C_DxEPINT_INTknEPMis (1 << 5) +#define S3C_DxEPINT_INTknTXFEmp (1 << 4) +#define S3C_DxEPINT_OUTTknEPdis (1 << 4) +#define S3C_DxEPINT_Timeout (1 << 3) +#define S3C_DxEPINT_Setup (1 << 3) +#define S3C_DxEPINT_AHBErr (1 << 2) +#define S3C_DxEPINT_EPDisbld (1 << 1) +#define S3C_DxEPINT_XferCompl (1 << 0) + +#define S3C_DIEPTSIZ0 S3C_HSOTG_REG(0x910) + +#define S3C_DIEPTSIZ0_PktCnt_MASK (0x3 << 19) +#define S3C_DIEPTSIZ0_PktCnt_SHIFT (19) +#define S3C_DIEPTSIZ0_PktCnt_LIMIT (0x3) +#define S3C_DIEPTSIZ0_PktCnt(_x) ((_x) << 19) + +#define S3C_DIEPTSIZ0_XferSize_MASK (0x7f << 0) +#define S3C_DIEPTSIZ0_XferSize_SHIFT (0) +#define S3C_DIEPTSIZ0_XferSize_LIMIT (0x7f) +#define S3C_DIEPTSIZ0_XferSize(_x) ((_x) << 0) + + +#define DOEPTSIZ0 S3C_HSOTG_REG(0xB10) +#define S3C_DOEPTSIZ0_SUPCnt_MASK (0x3 << 29) +#define S3C_DOEPTSIZ0_SUPCnt_SHIFT (29) +#define S3C_DOEPTSIZ0_SUPCnt_LIMIT (0x3) +#define S3C_DOEPTSIZ0_SUPCnt(_x) ((_x) << 29) + +#define S3C_DOEPTSIZ0_PktCnt (1 << 19) +#define S3C_DOEPTSIZ0_XferSize_MASK (0x7f << 0) +#define S3C_DOEPTSIZ0_XferSize_SHIFT (0) + +#define S3C_DIEPTSIZ(_a) S3C_HSOTG_REG(0x910 + ((_a) * 0x20)) +#define S3C_DOEPTSIZ(_a) S3C_HSOTG_REG(0xB10 + ((_a) * 0x20)) + +#define S3C_DxEPTSIZ_MC_MASK (0x3 << 29) +#define S3C_DxEPTSIZ_MC_SHIFT (29) +#define S3C_DxEPTSIZ_MC_LIMIT (0x3) +#define S3C_DxEPTSIZ_MC(_x) ((_x) << 29) + +#define S3C_DxEPTSIZ_PktCnt_MASK (0x3ff << 19) +#define S3C_DxEPTSIZ_PktCnt_SHIFT (19) +#define S3C_DxEPTSIZ_PktCnt_GET(_v) (((_v) >> 19) & 0x3ff) +#define S3C_DxEPTSIZ_PktCnt_LIMIT (0x3ff) +#define S3C_DxEPTSIZ_PktCnt(_x) ((_x) << 19) + +#define S3C_DxEPTSIZ_XferSize_MASK (0x7ffff << 0) +#define S3C_DxEPTSIZ_XferSize_SHIFT (0) +#define S3C_DxEPTSIZ_XferSize_GET(_v) (((_v) >> 0) & 0x7ffff) +#define S3C_DxEPTSIZ_XferSize_LIMIT (0x7ffff) +#define S3C_DxEPTSIZ_XferSize(_x) ((_x) << 0) + + +#define S3C_DIEPDMA(_a) S3C_HSOTG_REG(0x914 + ((_a) * 0x20)) +#define S3C_DOEPDMA(_a) S3C_HSOTG_REG(0xB14 + ((_a) * 0x20)) + +#define S3C_EPFIFO(_a) S3C_HSOTG_REG(0x1000 + ((_a) * 0x1000)) + +#endif /* __PLAT_S3C64XX_REGS_USB_HSOTG_H */ diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 5de9b4f21713..924bb7a1cecf 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -272,6 +272,20 @@ config USB_PXA27X default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_S3C_HSOTG + boolean "S3C HS/OtG USB Device controller" + depends on S3C_DEV_USB_HSOTG + select USB_GADGET_S3C_HSOTG_PIO + help + The Samsung S3C64XX USB2.0 high-speed gadget controller + integrated into the S3C64XX series SoC. + +config USB_S3C_HSOTG + tristate + depends on USB_GADGET_S3C_HSOTG + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_S3C2410 boolean "S3C2410 USB Device Controller" depends on ARCH_S3C2410 diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index c3fe06396b74..9be2fbd6f5c2 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -25,6 +25,7 @@ endif obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o +obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o # # USB gadget drivers diff --git a/drivers/usb/gadget/s3c-hsotg.c b/drivers/usb/gadget/s3c-hsotg.c new file mode 100644 index 000000000000..50c71aae2cc2 --- /dev/null +++ b/drivers/usb/gadget/s3c-hsotg.c @@ -0,0 +1,3269 @@ +/* linux/drivers/usb/gadget/s3c-hsotg.c + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * S3C USB2.0 High-speed / OtG driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/delay.h> +#include <linux/io.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include <mach/map.h> + +#include <plat/regs-usb-hsotg-phy.h> +#include <plat/regs-usb-hsotg.h> +#include <plat/regs-sys.h> +#include <plat/udc-hs.h> + +#define DMA_ADDR_INVALID (~((dma_addr_t)0)) + +/* EP0_MPS_LIMIT + * + * Unfortunately there seems to be a limit of the amount of data that can + * be transfered by IN transactions on EP0. This is either 127 bytes or 3 + * packets (which practially means 1 packet and 63 bytes of data) when the + * MPS is set to 64. + * + * This means if we are wanting to move >127 bytes of data, we need to + * split the transactions up, but just doing one packet at a time does + * not work (this may be an implicit DATA0 PID on first packet of the + * transaction) and doing 2 packets is outside the controller's limits. + * + * If we try to lower the MPS size for EP0, then no transfers work properly + * for EP0, and the system will fail basic enumeration. As no cause for this + * has currently been found, we cannot support any large IN transfers for + * EP0. + */ +#define EP0_MPS_LIMIT 64 + +struct s3c_hsotg; +struct s3c_hsotg_req; + +/** + * struct s3c_hsotg_ep - driver endpoint definition. + * @ep: The gadget layer representation of the endpoint. + * @name: The driver generated name for the endpoint. + * @queue: Queue of requests for this endpoint. + * @parent: Reference back to the parent device structure. + * @req: The current request that the endpoint is processing. This is + * used to indicate an request has been loaded onto the endpoint + * and has yet to be completed (maybe due to data move, or simply + * awaiting an ack from the core all the data has been completed). + * @debugfs: File entry for debugfs file for this endpoint. + * @lock: State lock to protect contents of endpoint. + * @dir_in: Set to true if this endpoint is of the IN direction, which + * means that it is sending data to the Host. + * @index: The index for the endpoint registers. + * @name: The name array passed to the USB core. + * @halted: Set if the endpoint has been halted. + * @periodic: Set if this is a periodic ep, such as Interrupt + * @sent_zlp: Set if we've sent a zero-length packet. + * @total_data: The total number of data bytes done. + * @fifo_size: The size of the FIFO (for periodic IN endpoints) + * @fifo_load: The amount of data loaded into the FIFO (periodic IN) + * @last_load: The offset of data for the last start of request. + * @size_loaded: The last loaded size for DxEPTSIZE for periodic IN + * + * This is the driver's state for each registered enpoint, allowing it + * to keep track of transactions that need doing. Each endpoint has a + * lock to protect the state, to try and avoid using an overall lock + * for the host controller as much as possible. + * + * For periodic IN endpoints, we have fifo_size and fifo_load to try + * and keep track of the amount of data in the periodic FIFO for each + * of these as we don't have a status register that tells us how much + * is in each of them. + */ +struct s3c_hsotg_ep { + struct usb_ep ep; + struct list_head queue; + struct s3c_hsotg *parent; + struct s3c_hsotg_req *req; + struct dentry *debugfs; + + spinlock_t lock; + + unsigned long total_data; + unsigned int size_loaded; + unsigned int last_load; + unsigned int fifo_load; + unsigned short fifo_size; + + unsigned char dir_in; + unsigned char index; + + unsigned int halted:1; + unsigned int periodic:1; + unsigned int sent_zlp:1; + + char name[10]; +}; + +#define S3C_HSOTG_EPS (8+1) /* limit to 9 for the moment */ + +/** + * struct s3c_hsotg - driver state. + * @dev: The parent device supplied to the probe function + * @driver: USB gadget driver + * @plat: The platform specific configuration data. + * @regs: The memory area mapped for accessing registers. + * @regs_res: The resource that was allocated when claiming register space. + * @irq: The IRQ number we are using + * @debug_root: root directrory for debugfs. + * @debug_file: main status file for debugfs. + * @debug_fifo: FIFO status file for debugfs. + * @ep0_reply: Request used for ep0 reply. + * @ep0_buff: Buffer for EP0 reply data, if needed. + * @ctrl_buff: Buffer for EP0 control requests. + * @ctrl_req: Request for EP0 control packets. + * @eps: The endpoints being supplied to the gadget framework + */ +struct s3c_hsotg { + struct device *dev; + struct usb_gadget_driver *driver; + struct s3c_hsotg_plat *plat; + + void __iomem *regs; + struct resource *regs_res; + int irq; + + struct dentry *debug_root; + struct dentry *debug_file; + struct dentry *debug_fifo; + + struct usb_request *ep0_reply; + struct usb_request *ctrl_req; + u8 ep0_buff[8]; + u8 ctrl_buff[8]; + + struct usb_gadget gadget; + struct s3c_hsotg_ep eps[]; +}; + +/** + * struct s3c_hsotg_req - data transfer request + * @req: The USB gadget request + * @queue: The list of requests for the endpoint this is queued for. + * @in_progress: Has already had size/packets written to core + * @mapped: DMA buffer for this request has been mapped via dma_map_single(). + */ +struct s3c_hsotg_req { + struct usb_request req; + struct list_head queue; + unsigned char in_progress; + unsigned char mapped; +}; + +/* conversion functions */ +static inline struct s3c_hsotg_req *our_req(struct usb_request *req) +{ + return container_of(req, struct s3c_hsotg_req, req); +} + +static inline struct s3c_hsotg_ep *our_ep(struct usb_ep *ep) +{ + return container_of(ep, struct s3c_hsotg_ep, ep); +} + +static inline struct s3c_hsotg *to_hsotg(struct usb_gadget *gadget) +{ + return container_of(gadget, struct s3c_hsotg, gadget); +} + +static inline void __orr32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) | val, ptr); +} + +static inline void __bic32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) & ~val, ptr); +} + +/* forward decleration of functions */ +static void s3c_hsotg_dump(struct s3c_hsotg *hsotg); + +/** + * using_dma - return the DMA status of the driver. + * @hsotg: The driver state. + * + * Return true if we're using DMA. + * + * Currently, we have the DMA support code worked into everywhere + * that needs it, but the AMBA DMA implementation in the hardware can + * only DMA from 32bit aligned addresses. This means that gadgets such + * as the CDC Ethernet cannot work as they often pass packets which are + * not 32bit aligned. + * + * Unfortunately the choice to use DMA or not is global to the controller + * and seems to be only settable when the controller is being put through + * a core reset. This means we either need to fix the gadgets to take + * account of DMA alignment, or add bounce buffers (yuerk). + * + * Until this issue is sorted out, we always return 'false'. + */ +static inline bool using_dma(struct s3c_hsotg *hsotg) +{ + return false; /* support is not complete */ +} + +/** + * s3c_hsotg_en_gsint - enable one or more of the general interrupt + * @hsotg: The device state + * @ints: A bitmask of the interrupts to enable + */ +static void s3c_hsotg_en_gsint(struct s3c_hsotg *hsotg, u32 ints) +{ + u32 gsintmsk = readl(hsotg->regs + S3C_GINTMSK); + u32 new_gsintmsk; + + new_gsintmsk = gsintmsk | ints; + + if (new_gsintmsk != gsintmsk) { + dev_dbg(hsotg->dev, "gsintmsk now 0x%08x\n", new_gsintmsk); + writel(new_gsintmsk, hsotg->regs + S3C_GINTMSK); + } +} + +/** + * s3c_hsotg_disable_gsint - disable one or more of the general interrupt + * @hsotg: The device state + * @ints: A bitmask of the interrupts to enable + */ +static void s3c_hsotg_disable_gsint(struct s3c_hsotg *hsotg, u32 ints) +{ + u32 gsintmsk = readl(hsotg->regs + S3C_GINTMSK); + u32 new_gsintmsk; + + new_gsintmsk = gsintmsk & ~ints; + + if (new_gsintmsk != gsintmsk) + writel(new_gsintmsk, hsotg->regs + S3C_GINTMSK); +} + +/** + * s3c_hsotg_ctrl_epint - enable/disable an endpoint irq + * @hsotg: The device state + * @ep: The endpoint index + * @dir_in: True if direction is in. + * @en: The enable value, true to enable + * + * Set or clear the mask for an individual endpoint's interrupt + * request. + */ +static void s3c_hsotg_ctrl_epint(struct s3c_hsotg *hsotg, + unsigned int ep, unsigned int dir_in, + unsigned int en) +{ + unsigned long flags; + u32 bit = 1 << ep; + u32 daint; + + if (!dir_in) + bit <<= 16; + + local_irq_save(flags); + daint = readl(hsotg->regs + S3C_DAINTMSK); + if (en) + daint |= bit; + else + daint &= ~bit; + writel(daint, hsotg->regs + S3C_DAINTMSK); + local_irq_restore(flags); +} + +/** + * s3c_hsotg_init_fifo - initialise non-periodic FIFOs + * @hsotg: The device instance. + */ +static void s3c_hsotg_init_fifo(struct s3c_hsotg *hsotg) +{ + /* the ryu 2.6.24 release ahs + writel(0x1C0, hsotg->regs + S3C_GRXFSIZ); + writel(S3C_GNPTXFSIZ_NPTxFStAddr(0x200) | + S3C_GNPTXFSIZ_NPTxFDep(0x1C0), + hsotg->regs + S3C_GNPTXFSIZ); + */ + + /* set FIFO sizes to 2048/0x1C0 */ + + writel(2048, hsotg->regs + S3C_GRXFSIZ); + writel(S3C_GNPTXFSIZ_NPTxFStAddr(2048) | + S3C_GNPTXFSIZ_NPTxFDep(0x1C0), + hsotg->regs + S3C_GNPTXFSIZ); +} + +/** + * @ep: USB endpoint to allocate request for. + * @flags: Allocation flags + * + * Allocate a new USB request structure appropriate for the specified endpoint + */ +struct usb_request *s3c_hsotg_ep_alloc_request(struct usb_ep *ep, gfp_t flags) +{ + struct s3c_hsotg_req *req; + + req = kzalloc(sizeof(struct s3c_hsotg_req), flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + req->req.dma = DMA_ADDR_INVALID; + return &req->req; +} + +/** + * is_ep_periodic - return true if the endpoint is in periodic mode. + * @hs_ep: The endpoint to query. + * + * Returns true if the endpoint is in periodic mode, meaning it is being + * used for an Interrupt or ISO transfer. + */ +static inline int is_ep_periodic(struct s3c_hsotg_ep *hs_ep) +{ + return hs_ep->periodic; +} + +/** + * s3c_hsotg_unmap_dma - unmap the DMA memory being used for the request + * @hsotg: The device state. + * @hs_ep: The endpoint for the request + * @hs_req: The request being processed. + * + * This is the reverse of s3c_hsotg_map_dma(), called for the completion + * of a request to ensure the buffer is ready for access by the caller. +*/ +static void s3c_hsotg_unmap_dma(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct s3c_hsotg_req *hs_req) +{ + struct usb_request *req = &hs_req->req; + enum dma_data_direction dir; + + dir = hs_ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + + /* ignore this if we're not moving any data */ + if (hs_req->req.length == 0) + return; + + if (hs_req->mapped) { + /* we mapped this, so unmap and remove the dma */ + + dma_unmap_single(hsotg->dev, req->dma, req->length, dir); + + req->dma = DMA_ADDR_INVALID; + hs_req->mapped = 0; + } else { + dma_sync_single(hsotg->dev, req->dma, req->length, dir); + } +} + +/** + * s3c_hsotg_write_fifo - write packet Data to the TxFIFO + * @hsotg: The controller state. + * @hs_ep: The endpoint we're going to write for. + * @hs_req: The request to write data for. + * + * This is called when the TxFIFO has some space in it to hold a new + * transmission and we have something to give it. The actual setup of + * the data size is done elsewhere, so all we have to do is to actually + * write the data. + * + * The return value is zero if there is more space (or nothing was done) + * otherwise -ENOSPC is returned if the FIFO space was used up. + * + * This routine is only needed for PIO +*/ +static int s3c_hsotg_write_fifo(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct s3c_hsotg_req *hs_req) +{ + bool periodic = is_ep_periodic(hs_ep); + u32 gnptxsts = readl(hsotg->regs + S3C_GNPTXSTS); + int buf_pos = hs_req->req.actual; + int to_write = hs_ep->size_loaded; + void *data; + int can_write; + int pkt_round; + + to_write -= (buf_pos - hs_ep->last_load); + + /* if there's nothing to write, get out early */ + if (to_write == 0) + return 0; + + if (periodic) { + u32 epsize = readl(hsotg->regs + S3C_DIEPTSIZ(hs_ep->index)); + int size_left; + int size_done; + + /* work out how much data was loaded so we can calculate + * how much data is left in the fifo. */ + + size_left = S3C_DxEPTSIZ_XferSize_GET(epsize); + + dev_dbg(hsotg->dev, "%s: left=%d, load=%d, fifo=%d, size %d\n", + __func__, size_left, + hs_ep->size_loaded, hs_ep->fifo_load, hs_ep->fifo_size); + + /* how much of the data has moved */ + size_done = hs_ep->size_loaded - size_left; + + /* how much data is left in the fifo */ + can_write = hs_ep->fifo_load - size_done; + dev_dbg(hsotg->dev, "%s: => can_write1=%d\n", + __func__, can_write); + + can_write = hs_ep->fifo_size - can_write; + dev_dbg(hsotg->dev, "%s: => can_write2=%d\n", + __func__, can_write); + + if (can_write <= 0) { + s3c_hsotg_en_gsint(hsotg, S3C_GINTSTS_PTxFEmp); + return -ENOSPC; + } + } else { + if (S3C_GNPTXSTS_NPTxQSpcAvail_GET(gnptxsts) == 0) { + dev_dbg(hsotg->dev, + "%s: no queue slots available (0x%08x)\n", + __func__, gnptxsts); + + s3c_hsotg_en_gsint(hsotg, S3C_GINTSTS_NPTxFEmp); + return -ENOSPC; + } + + can_write = S3C_GNPTXSTS_NPTxFSpcAvail_GET(gnptxsts); + } + + dev_dbg(hsotg->dev, "%s: GNPTXSTS=%08x, can=%d, to=%d, mps %d\n", + __func__, gnptxsts, can_write, to_write, hs_ep->ep.maxpacket); + + /* limit to 512 bytes of data, it seems at least on the non-periodic + * FIFO, requests of >512 cause the endpoint to get stuck with a + * fragment of the end of the transfer in it. + */ + if (can_write > 512) + can_write = 512; + + /* see if we can write data */ + + if (to_write > can_write) { + to_write = can_write; + pkt_round = to_write % hs_ep->ep.maxpacket; + + /* Not sure, but we probably shouldn't be writing partial + * packets into the FIFO, so round the write down to an + * exact number of packets. + * + * Note, we do not currently check to see if we can ever + * write a full packet or not to the FIFO. + */ + + if (pkt_round) + to_write -= pkt_round; + + /* enable correct FIFO interrupt to alert us when there + * is more room left. */ + + s3c_hsotg_en_gsint(hsotg, + periodic ? S3C_GINTSTS_PTxFEmp : + S3C_GINTSTS_NPTxFEmp); + } + + dev_dbg(hsotg->dev, "write %d/%d, can_write %d, done %d\n", + to_write, hs_req->req.length, can_write, buf_pos); + + if (to_write <= 0) + return -ENOSPC; + + hs_req->req.actual = buf_pos + to_write; + hs_ep->total_data += to_write; + + if (periodic) + hs_ep->fifo_load += to_write; + + to_write = DIV_ROUND_UP(to_write, 4); + data = hs_req->req.buf + buf_pos; + + writesl(hsotg->regs + S3C_EPFIFO(hs_ep->index), data, to_write); + + return (to_write >= can_write) ? -ENOSPC : 0; +} + +/** + * get_ep_limit - get the maximum data legnth for this endpoint + * @hs_ep: The endpoint + * + * Return the maximum data that can be queued in one go on a given endpoint + * so that transfers that are too long can be split. + */ +static unsigned get_ep_limit(struct s3c_hsotg_ep *hs_ep) +{ + int index = hs_ep->index; + unsigned maxsize; + unsigned maxpkt; + + if (index != 0) { + maxsize = S3C_DxEPTSIZ_XferSize_LIMIT + 1; + maxpkt = S3C_DxEPTSIZ_PktCnt_LIMIT + 1; + } else { + if (hs_ep->dir_in) { + /* maxsize = S3C_DIEPTSIZ0_XferSize_LIMIT + 1; */ + maxsize = 64+64+1; + maxpkt = S3C_DIEPTSIZ0_PktCnt_LIMIT + 1; + } else { + maxsize = 0x3f; + maxpkt = 2; + } + } + + /* we made the constant loading easier above by using +1 */ + maxpkt--; + maxsize--; + + /* constrain by packet count if maxpkts*pktsize is greater + * than the length register size. */ + + if ((maxpkt * hs_ep->ep.maxpacket) < maxsize) + maxsize = maxpkt * hs_ep->ep.maxpacket; + + return maxsize; +} + +/** + * s3c_hsotg_start_req - start a USB request from an endpoint's queue + * @hsotg: The controller state. + * @hs_ep: The endpoint to process a request for + * @hs_req: The request to start. + * @continuing: True if we are doing more for the current request. + * + * Start the given request running by setting the endpoint registers + * appropriately, and writing any data to the FIFOs. + */ +static void s3c_hsotg_start_req(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct s3c_hsotg_req *hs_req, + bool continuing) +{ + struct usb_request *ureq = &hs_req->req; + int index = hs_ep->index; + int dir_in = hs_ep->dir_in; + u32 epctrl_reg; + u32 epsize_reg; + u32 epsize; + u32 ctrl; + unsigned length; + unsigned packets; + unsigned maxreq; + + if (index != 0) { + if (hs_ep->req && !continuing) { + dev_err(hsotg->dev, "%s: active request\n", __func__); + WARN_ON(1); + return; + } else if (hs_ep->req != hs_req && continuing) { + dev_err(hsotg->dev, + "%s: continue different req\n", __func__); + WARN_ON(1); + return; + } + } + + epctrl_reg = dir_in ? S3C_DIEPCTL(index) : S3C_DOEPCTL(index); + epsize_reg = dir_in ? S3C_DIEPTSIZ(index) : S3C_DOEPTSIZ(index); + + dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x, ep %d, dir %s\n", + __func__, readl(hsotg->regs + epctrl_reg), index, + hs_ep->dir_in ? "in" : "out"); + + length = ureq->length - ureq->actual; + + if (0) + dev_dbg(hsotg->dev, + "REQ buf %p len %d dma 0x%08x noi=%d zp=%d snok=%d\n", + ureq->buf, length, ureq->dma, + ureq->no_interrupt, ureq->zero, ureq->short_not_ok); + + maxreq = get_ep_limit(hs_ep); + if (length > maxreq) { + int round = maxreq % hs_ep->ep.maxpacket; + + dev_dbg(hsotg->dev, "%s: length %d, max-req %d, r %d\n", + __func__, length, maxreq, round); + + /* round down to multiple of packets */ + if (round) + maxreq -= round; + + length = maxreq; + } + + if (length) + packets = DIV_ROUND_UP(length, hs_ep->ep.maxpacket); + else + packets = 1; /* send one packet if length is zero. */ + + if (dir_in && index != 0) + epsize = S3C_DxEPTSIZ_MC(1); + else + epsize = 0; + + if (index != 0 && ureq->zero) { + /* test for the packets being exactly right for the + * transfer */ + + if (length == (packets * hs_ep->ep.maxpacket)) + packets++; + } + + epsize |= S3C_DxEPTSIZ_PktCnt(packets); + epsize |= S3C_DxEPTSIZ_XferSize(length); + + dev_dbg(hsotg->dev, "%s: %d@%d/%d, 0x%08x => 0x%08x\n", + __func__, packets, length, ureq->length, epsize, epsize_reg); + + /* store the request as the current one we're doing */ + hs_ep->req = hs_req; + + /* write size / packets */ + writel(epsize, hsotg->regs + epsize_reg); + + ctrl = readl(hsotg->regs + epctrl_reg); + + if (ctrl & S3C_DxEPCTL_Stall) { + dev_warn(hsotg->dev, "%s: ep%d is stalled\n", __func__, index); + + /* not sure what we can do here, if it is EP0 then we should + * get this cleared once the endpoint has transmitted the + * STALL packet, otherwise it needs to be cleared by the + * host. + */ + } + + if (using_dma(hsotg)) { + unsigned int dma_reg; + + /* write DMA address to control register, buffer already + * synced by s3c_hsotg_ep_queue(). */ + + dma_reg = dir_in ? S3C_DIEPDMA(index) : S3C_DOEPDMA(index); + writel(ureq->dma, hsotg->regs + dma_reg); + + dev_dbg(hsotg->dev, "%s: 0x%08x => 0x%08x\n", + __func__, ureq->dma, dma_reg); + } + + ctrl |= S3C_DxEPCTL_EPEna; /* ensure ep enabled */ + ctrl |= S3C_DxEPCTL_USBActEp; + ctrl |= S3C_DxEPCTL_CNAK; /* clear NAK set by core */ + + dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); + writel(ctrl, hsotg->regs + epctrl_reg); + + /* set these, it seems that DMA support increments past the end + * of the packet buffer so we need to calculate the length from + * this information. */ + hs_ep->size_loaded = length; + hs_ep->last_load = ureq->actual; + + if (dir_in && !using_dma(hsotg)) { + /* set these anyway, we may need them for non-periodic in */ + hs_ep->fifo_load = 0; + + s3c_hsotg_write_fifo(hsotg, hs_ep, hs_req); + } + + /* clear the INTknTXFEmpMsk when we start request, more as a aide + * to debugging to see what is going on. */ + if (dir_in) + writel(S3C_DIEPMSK_INTknTXFEmpMsk, + hsotg->regs + S3C_DIEPINT(index)); + + /* Note, trying to clear the NAK here causes problems with transmit + * on the S3C6400 ending up with the TXFIFO becomming full. */ + + /* check ep is enabled */ + if (!(readl(hsotg->regs + epctrl_reg) & S3C_DxEPCTL_EPEna)) + dev_warn(hsotg->dev, + "ep%d: failed to become enabled (DxEPCTL=0x%08x)?\n", + index, readl(hsotg->regs + epctrl_reg)); + + dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", + __func__, readl(hsotg->regs + epctrl_reg)); +} + +/** + * s3c_hsotg_map_dma - map the DMA memory being used for the request + * @hsotg: The device state. + * @hs_ep: The endpoint the request is on. + * @req: The request being processed. + * + * We've been asked to queue a request, so ensure that the memory buffer + * is correctly setup for DMA. If we've been passed an extant DMA address + * then ensure the buffer has been synced to memory. If our buffer has no + * DMA memory, then we map the memory and mark our request to allow us to + * cleanup on completion. +*/ +static int s3c_hsotg_map_dma(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct usb_request *req) +{ + enum dma_data_direction dir; + struct s3c_hsotg_req *hs_req = our_req(req); + + dir = hs_ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + + /* if the length is zero, ignore the DMA data */ + if (hs_req->req.length == 0) + return 0; + + if (req->dma == DMA_ADDR_INVALID) { + dma_addr_t dma; + + dma = dma_map_single(hsotg->dev, req->buf, req->length, dir); + + if (unlikely(dma_mapping_error(hsotg->dev, dma))) + goto dma_error; + + if (dma & 3) { + dev_err(hsotg->dev, "%s: unaligned dma buffer\n", + __func__); + + dma_unmap_single(hsotg->dev, dma, req->length, dir); + return -EINVAL; + } + + hs_req->mapped = 1; + req->dma = dma; + } else { + dma_sync_single(hsotg->dev, req->dma, req->length, dir); + hs_req->mapped = 0; + } + + return 0; + +dma_error: + dev_err(hsotg->dev, "%s: failed to map buffer %p, %d bytes\n", + __func__, req->buf, req->length); + + return -EIO; +} + +static int s3c_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t gfp_flags) +{ + struct s3c_hsotg_req *hs_req = our_req(req); + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hs = hs_ep->parent; + unsigned long irqflags; + bool first; + + dev_dbg(hs->dev, "%s: req %p: %d@%p, noi=%d, zero=%d, snok=%d\n", + ep->name, req, req->length, req->buf, req->no_interrupt, + req->zero, req->short_not_ok); + + /* initialise status of the request */ + INIT_LIST_HEAD(&hs_req->queue); + req->actual = 0; + req->status = -EINPROGRESS; + + /* if we're using DMA, sync the buffers as necessary */ + if (using_dma(hs)) { + int ret = s3c_hsotg_map_dma(hs, hs_ep, req); + if (ret) + return ret; + } + + spin_lock_irqsave(&hs_ep->lock, irqflags); + + first = list_empty(&hs_ep->queue); + list_add_tail(&hs_req->queue, &hs_ep->queue); + + if (first) + s3c_hsotg_start_req(hs, hs_ep, hs_req, false); + + spin_unlock_irqrestore(&hs_ep->lock, irqflags); + + return 0; +} + +static void s3c_hsotg_ep_free_request(struct usb_ep *ep, + struct usb_request *req) +{ + struct s3c_hsotg_req *hs_req = our_req(req); + + kfree(hs_req); +} + +/** + * s3c_hsotg_complete_oursetup - setup completion callback + * @ep: The endpoint the request was on. + * @req: The request completed. + * + * Called on completion of any requests the driver itself + * submitted that need cleaning up. + */ +static void s3c_hsotg_complete_oursetup(struct usb_ep *ep, + struct usb_request *req) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hsotg = hs_ep->parent; + + dev_dbg(hsotg->dev, "%s: ep %p, req %p\n", __func__, ep, req); + + s3c_hsotg_ep_free_request(ep, req); +} + +/** + * ep_from_windex - convert control wIndex value to endpoint + * @hsotg: The driver state. + * @windex: The control request wIndex field (in host order). + * + * Convert the given wIndex into a pointer to an driver endpoint + * structure, or return NULL if it is not a valid endpoint. +*/ +static struct s3c_hsotg_ep *ep_from_windex(struct s3c_hsotg *hsotg, + u32 windex) +{ + struct s3c_hsotg_ep *ep = &hsotg->eps[windex & 0x7F]; + int dir = (windex & USB_DIR_IN) ? 1 : 0; + int idx = windex & 0x7F; + + if (windex >= 0x100) + return NULL; + + if (idx > S3C_HSOTG_EPS) + return NULL; + + if (idx && ep->dir_in != dir) + return NULL; + + return ep; +} + +/** + * s3c_hsotg_send_reply - send reply to control request + * @hsotg: The device state + * @ep: Endpoint 0 + * @buff: Buffer for request + * @length: Length of reply. + * + * Create a request and queue it on the given endpoint. This is useful as + * an internal method of sending replies to certain control requests, etc. + */ +static int s3c_hsotg_send_reply(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *ep, + void *buff, + int length) +{ + struct usb_request *req; + int ret; + + dev_dbg(hsotg->dev, "%s: buff %p, len %d\n", __func__, buff, length); + + req = s3c_hsotg_ep_alloc_request(&ep->ep, GFP_ATOMIC); + hsotg->ep0_reply = req; + if (!req) { + dev_warn(hsotg->dev, "%s: cannot alloc req\n", __func__); + return -ENOMEM; + } + + req->buf = hsotg->ep0_buff; + req->length = length; + req->zero = 1; /* always do zero-length final transfer */ + req->complete = s3c_hsotg_complete_oursetup; + + if (length) + memcpy(req->buf, buff, length); + else + ep->sent_zlp = 1; + + ret = s3c_hsotg_ep_queue(&ep->ep, req, GFP_ATOMIC); + if (ret) { + dev_warn(hsotg->dev, "%s: cannot queue req\n", __func__); + return ret; + } + + return 0; +} + +/** + * s3c_hsotg_process_req_status - process request GET_STATUS + * @hsotg: The device state + * @ctrl: USB control request + */ +static int s3c_hsotg_process_req_status(struct s3c_hsotg *hsotg, + struct usb_ctrlrequest *ctrl) +{ + struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_ep *ep; + __le16 reply; + int ret; + + dev_dbg(hsotg->dev, "%s: USB_REQ_GET_STATUS\n", __func__); + + if (!ep0->dir_in) { + dev_warn(hsotg->dev, "%s: direction out?\n", __func__); + return -EINVAL; + } + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + reply = cpu_to_le16(0); /* bit 0 => self powered, + * bit 1 => remote wakeup */ + break; + + case USB_RECIP_INTERFACE: + /* currently, the data result should be zero */ + reply = cpu_to_le16(0); + break; + + case USB_RECIP_ENDPOINT: + ep = ep_from_windex(hsotg, le16_to_cpu(ctrl->wIndex)); + if (!ep) + return -ENOENT; + + reply = cpu_to_le16(ep->halted ? 1 : 0); + break; + + default: + return 0; + } + + if (le16_to_cpu(ctrl->wLength) != 2) + return -EINVAL; + + ret = s3c_hsotg_send_reply(hsotg, ep0, &reply, 2); + if (ret) { + dev_err(hsotg->dev, "%s: failed to send reply\n", __func__); + return ret; + } + + return 1; +} + +static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value); + +/** + * s3c_hsotg_process_req_featire - process request {SET,CLEAR}_FEATURE + * @hsotg: The device state + * @ctrl: USB control request + */ +static int s3c_hsotg_process_req_feature(struct s3c_hsotg *hsotg, + struct usb_ctrlrequest *ctrl) +{ + bool set = (ctrl->bRequest == USB_REQ_SET_FEATURE); + struct s3c_hsotg_ep *ep; + + dev_dbg(hsotg->dev, "%s: %s_FEATURE\n", + __func__, set ? "SET" : "CLEAR"); + + if (ctrl->bRequestType == USB_RECIP_ENDPOINT) { + ep = ep_from_windex(hsotg, le16_to_cpu(ctrl->wIndex)); + if (!ep) { + dev_dbg(hsotg->dev, "%s: no endpoint for 0x%04x\n", + __func__, le16_to_cpu(ctrl->wIndex)); + return -ENOENT; + } + + switch (le16_to_cpu(ctrl->wValue)) { + case USB_ENDPOINT_HALT: + s3c_hsotg_ep_sethalt(&ep->ep, set); + break; + + default: + return -ENOENT; + } + } else + return -ENOENT; /* currently only deal with endpoint */ + + return 1; +} + +/** + * s3c_hsotg_process_control - process a control request + * @hsotg: The device state + * @ctrl: The control request received + * + * The controller has received the SETUP phase of a control request, and + * needs to work out what to do next (and whether to pass it on to the + * gadget driver). + */ +static void s3c_hsotg_process_control(struct s3c_hsotg *hsotg, + struct usb_ctrlrequest *ctrl) +{ + struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + int ret = 0; + u32 dcfg; + + ep0->sent_zlp = 0; + + dev_dbg(hsotg->dev, "ctrl Req=%02x, Type=%02x, V=%04x, L=%04x\n", + ctrl->bRequest, ctrl->bRequestType, + ctrl->wValue, ctrl->wLength); + + /* record the direction of the request, for later use when enquing + * packets onto EP0. */ + + ep0->dir_in = (ctrl->bRequestType & USB_DIR_IN) ? 1 : 0; + dev_dbg(hsotg->dev, "ctrl: dir_in=%d\n", ep0->dir_in); + + /* if we've no data with this request, then the last part of the + * transaction is going to implicitly be IN. */ + if (ctrl->wLength == 0) + ep0->dir_in = 1; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + dcfg = readl(hsotg->regs + S3C_DCFG); + dcfg &= ~S3C_DCFG_DevAddr_MASK; + dcfg |= ctrl->wValue << S3C_DCFG_DevAddr_SHIFT; + writel(dcfg, hsotg->regs + S3C_DCFG); + + dev_info(hsotg->dev, "new address %d\n", ctrl->wValue); + + ret = s3c_hsotg_send_reply(hsotg, ep0, NULL, 0); + return; + + case USB_REQ_GET_STATUS: + ret = s3c_hsotg_process_req_status(hsotg, ctrl); + break; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + ret = s3c_hsotg_process_req_feature(hsotg, ctrl); + break; + } + } + + /* as a fallback, try delivering it to the driver to deal with */ + + if (ret == 0 && hsotg->driver) { + ret = hsotg->driver->setup(&hsotg->gadget, ctrl); + if (ret < 0) + dev_dbg(hsotg->dev, "driver->setup() ret %d\n", ret); + } + + if (ret > 0) { + if (!ep0->dir_in) { + /* need to generate zlp in reply or take data */ + /* todo - deal with any data we might be sent? */ + ret = s3c_hsotg_send_reply(hsotg, ep0, NULL, 0); + } + } + + /* the request is either unhandlable, or is not formatted correctly + * so respond with a STALL for the status stage to indicate failure. + */ + + if (ret < 0) { + u32 reg; + u32 ctrl; + + dev_dbg(hsotg->dev, "ep0 stall (dir=%d)\n", ep0->dir_in); + reg = (ep0->dir_in) ? S3C_DIEPCTL0 : S3C_DOEPCTL0; + + /* S3C_DxEPCTL_Stall will be cleared by EP once it has + * taken effect, so no need to clear later. */ + + ctrl = readl(hsotg->regs + reg); + ctrl |= S3C_DxEPCTL_Stall; + ctrl |= S3C_DxEPCTL_CNAK; + writel(ctrl, hsotg->regs + reg); + + dev_dbg(hsotg->dev, + "writen DxEPCTL=0x%08x to %08x (DxEPCTL=0x%08x)\n", + ctrl, reg, readl(hsotg->regs + reg)); + + /* don't belive we need to anything more to get the EP + * to reply with a STALL packet */ + } +} + +static void s3c_hsotg_enqueue_setup(struct s3c_hsotg *hsotg); + +/** + * s3c_hsotg_complete_setup - completion of a setup transfer + * @ep: The endpoint the request was on. + * @req: The request completed. + * + * Called on completion of any requests the driver itself submitted for + * EP0 setup packets + */ +static void s3c_hsotg_complete_setup(struct usb_ep *ep, + struct usb_request *req) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hsotg = hs_ep->parent; + + if (req->status < 0) { + dev_dbg(hsotg->dev, "%s: failed %d\n", __func__, req->status); + return; + } + + if (req->actual == 0) + s3c_hsotg_enqueue_setup(hsotg); + else + s3c_hsotg_process_control(hsotg, req->buf); +} + +/** + * s3c_hsotg_enqueue_setup - start a request for EP0 packets + * @hsotg: The device state. + * + * Enqueue a request on EP0 if necessary to received any SETUP packets + * received from the host. + */ +static void s3c_hsotg_enqueue_setup(struct s3c_hsotg *hsotg) +{ + struct usb_request *req = hsotg->ctrl_req; + struct s3c_hsotg_req *hs_req = our_req(req); + int ret; + + dev_dbg(hsotg->dev, "%s: queueing setup request\n", __func__); + + req->zero = 0; + req->length = 8; + req->buf = hsotg->ctrl_buff; + req->complete = s3c_hsotg_complete_setup; + + if (!list_empty(&hs_req->queue)) { + dev_dbg(hsotg->dev, "%s already queued???\n", __func__); + return; + } + + hsotg->eps[0].dir_in = 0; + + ret = s3c_hsotg_ep_queue(&hsotg->eps[0].ep, req, GFP_ATOMIC); + if (ret < 0) { + dev_err(hsotg->dev, "%s: failed queue (%d)\n", __func__, ret); + /* Don't think there's much we can do other than watch the + * driver fail. */ + } +} + +/** + * get_ep_head - return the first request on the endpoint + * @hs_ep: The controller endpoint to get + * + * Get the first request on the endpoint. +*/ +static struct s3c_hsotg_req *get_ep_head(struct s3c_hsotg_ep *hs_ep) +{ + if (list_empty(&hs_ep->queue)) + return NULL; + + return list_first_entry(&hs_ep->queue, struct s3c_hsotg_req, queue); +} + +/** + * s3c_hsotg_complete_request - complete a request given to us + * @hsotg: The device state. + * @hs_ep: The endpoint the request was on. + * @hs_req: The request to complete. + * @result: The result code (0 => Ok, otherwise errno) + * + * The given request has finished, so call the necessary completion + * if it has one and then look to see if we can start a new request + * on the endpoint. + * + * Note, expects the ep to already be locked as appropriate. +*/ +static void s3c_hsotg_complete_request(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct s3c_hsotg_req *hs_req, + int result) +{ + bool restart; + + if (!hs_req) { + dev_dbg(hsotg->dev, "%s: nothing to complete?\n", __func__); + return; + } + + dev_dbg(hsotg->dev, "complete: ep %p %s, req %p, %d => %p\n", + hs_ep, hs_ep->ep.name, hs_req, result, hs_req->req.complete); + + /* only replace the status if we've not already set an error + * from a previous transaction */ + + if (hs_req->req.status == -EINPROGRESS) + hs_req->req.status = result; + + hs_ep->req = NULL; + list_del_init(&hs_req->queue); + + if (using_dma(hsotg)) + s3c_hsotg_unmap_dma(hsotg, hs_ep, hs_req); + + /* call the complete request with the locks off, just in case the + * request tries to queue more work for this endpoint. */ + + if (hs_req->req.complete) { + spin_unlock(&hs_ep->lock); + hs_req->req.complete(&hs_ep->ep, &hs_req->req); + spin_lock(&hs_ep->lock); + } + + /* Look to see if there is anything else to do. Note, the completion + * of the previous request may have caused a new request to be started + * so be careful when doing this. */ + + if (!hs_ep->req && result >= 0) { + restart = !list_empty(&hs_ep->queue); + if (restart) { + hs_req = get_ep_head(hs_ep); + s3c_hsotg_start_req(hsotg, hs_ep, hs_req, false); + } + } +} + +/** + * s3c_hsotg_complete_request_lock - complete a request given to us (locked) + * @hsotg: The device state. + * @hs_ep: The endpoint the request was on. + * @hs_req: The request to complete. + * @result: The result code (0 => Ok, otherwise errno) + * + * See s3c_hsotg_complete_request(), but called with the endpoint's + * lock held. +*/ +static void s3c_hsotg_complete_request_lock(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + struct s3c_hsotg_req *hs_req, + int result) +{ + unsigned long flags; + + spin_lock_irqsave(&hs_ep->lock, flags); + s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, result); + spin_unlock_irqrestore(&hs_ep->lock, flags); +} + +/** + * s3c_hsotg_rx_data - receive data from the FIFO for an endpoint + * @hsotg: The device state. + * @ep_idx: The endpoint index for the data + * @size: The size of data in the fifo, in bytes + * + * The FIFO status shows there is data to read from the FIFO for a given + * endpoint, so sort out whether we need to read the data into a request + * that has been made for that endpoint. + */ +static void s3c_hsotg_rx_data(struct s3c_hsotg *hsotg, int ep_idx, int size) +{ + struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep_idx]; + struct s3c_hsotg_req *hs_req = hs_ep->req; + void __iomem *fifo = hsotg->regs + S3C_EPFIFO(ep_idx); + int to_read; + int max_req; + int read_ptr; + + if (!hs_req) { + u32 epctl = readl(hsotg->regs + S3C_DOEPCTL(ep_idx)); + int ptr; + + dev_warn(hsotg->dev, + "%s: FIFO %d bytes on ep%d but no req (DxEPCTl=0x%08x)\n", + __func__, size, ep_idx, epctl); + + /* dump the data from the FIFO, we've nothing we can do */ + for (ptr = 0; ptr < size; ptr += 4) + (void)readl(fifo); + + return; + } + + spin_lock(&hs_ep->lock); + + to_read = size; + read_ptr = hs_req->req.actual; + max_req = hs_req->req.length - read_ptr; + + if (to_read > max_req) { + /* more data appeared than we where willing + * to deal with in this request. + */ + + /* currently we don't deal this */ + WARN_ON_ONCE(1); + } + + dev_dbg(hsotg->dev, "%s: read %d/%d, done %d/%d\n", + __func__, to_read, max_req, read_ptr, hs_req->req.length); + + hs_ep->total_data += to_read; + hs_req->req.actual += to_read; + to_read = DIV_ROUND_UP(to_read, 4); + + /* note, we might over-write the buffer end by 3 bytes depending on + * alignment of the data. */ + readsl(fifo, hs_req->req.buf + read_ptr, to_read); + + spin_unlock(&hs_ep->lock); +} + +/** + * s3c_hsotg_send_zlp - send zero-length packet on control endpoint + * @hsotg: The device instance + * @req: The request currently on this endpoint + * + * Generate a zero-length IN packet request for terminating a SETUP + * transaction. + * + * Note, since we don't write any data to the TxFIFO, then it is + * currently belived that we do not need to wait for any space in + * the TxFIFO. + */ +static void s3c_hsotg_send_zlp(struct s3c_hsotg *hsotg, + struct s3c_hsotg_req *req) +{ + u32 ctrl; + + if (!req) { + dev_warn(hsotg->dev, "%s: no request?\n", __func__); + return; + } + + if (req->req.length == 0) { + hsotg->eps[0].sent_zlp = 1; + s3c_hsotg_enqueue_setup(hsotg); + return; + } + + hsotg->eps[0].dir_in = 1; + hsotg->eps[0].sent_zlp = 1; + + dev_dbg(hsotg->dev, "sending zero-length packet\n"); + + /* issue a zero-sized packet to terminate this */ + writel(S3C_DxEPTSIZ_MC(1) | S3C_DxEPTSIZ_PktCnt(1) | + S3C_DxEPTSIZ_XferSize(0), hsotg->regs + S3C_DIEPTSIZ(0)); + + ctrl = readl(hsotg->regs + S3C_DIEPCTL0); + ctrl |= S3C_DxEPCTL_CNAK; /* clear NAK set by core */ + ctrl |= S3C_DxEPCTL_EPEna; /* ensure ep enabled */ + ctrl |= S3C_DxEPCTL_USBActEp; + writel(ctrl, hsotg->regs + S3C_DIEPCTL0); +} + +/** + * s3c_hsotg_handle_outdone - handle receiving OutDone/SetupDone from RXFIFO + * @hsotg: The device instance + * @epnum: The endpoint received from + * @was_setup: Set if processing a SetupDone event. + * + * The RXFIFO has delivered an OutDone event, which means that the data + * transfer for an OUT endpoint has been completed, either by a short + * packet or by the finish of a transfer. +*/ +static void s3c_hsotg_handle_outdone(struct s3c_hsotg *hsotg, + int epnum, bool was_setup) +{ + struct s3c_hsotg_ep *hs_ep = &hsotg->eps[epnum]; + struct s3c_hsotg_req *hs_req = hs_ep->req; + struct usb_request *req = &hs_req->req; + int result = 0; + + if (!hs_req) { + dev_dbg(hsotg->dev, "%s: no request active\n", __func__); + return; + } + + if (using_dma(hsotg)) { + u32 epsize = readl(hsotg->regs + S3C_DOEPTSIZ(epnum)); + unsigned size_done; + unsigned size_left; + + /* Calculate the size of the transfer by checking how much + * is left in the endpoint size register and then working it + * out from the amount we loaded for the transfer. + * + * We need to do this as DMA pointers are always 32bit aligned + * so may overshoot/undershoot the transfer. + */ + + size_left = S3C_DxEPTSIZ_XferSize_GET(epsize); + + size_done = hs_ep->size_loaded - size_left; + size_done += hs_ep->last_load; + + req->actual = size_done; + } + + if (req->actual < req->length && req->short_not_ok) { + dev_dbg(hsotg->dev, "%s: got %d/%d (short not ok) => error\n", + __func__, req->actual, req->length); + + /* todo - what should we return here? there's no one else + * even bothering to check the status. */ + } + + if (epnum == 0) { + if (!was_setup && req->complete != s3c_hsotg_complete_setup) + s3c_hsotg_send_zlp(hsotg, hs_req); + } + + s3c_hsotg_complete_request_lock(hsotg, hs_ep, hs_req, result); +} + +/** + * s3c_hsotg_read_frameno - read current frame number + * @hsotg: The device instance + * + * Return the current frame number +*/ +static u32 s3c_hsotg_read_frameno(struct s3c_hsotg *hsotg) +{ + u32 dsts; + + dsts = readl(hsotg->regs + S3C_DSTS); + dsts &= S3C_DSTS_SOFFN_MASK; + dsts >>= S3C_DSTS_SOFFN_SHIFT; + + return dsts; +} + +/** + * s3c_hsotg_handle_rx - RX FIFO has data + * @hsotg: The device instance + * + * The IRQ handler has detected that the RX FIFO has some data in it + * that requires processing, so find out what is in there and do the + * appropriate read. + * + * The RXFIFO is a true FIFO, the packets comming out are still in packet + * chunks, so if you have x packets received on an endpoint you'll get x + * FIFO events delivered, each with a packet's worth of data in it. + * + * When using DMA, we should not be processing events from the RXFIFO + * as the actual data should be sent to the memory directly and we turn + * on the completion interrupts to get notifications of transfer completion. + */ +void s3c_hsotg_handle_rx(struct s3c_hsotg *hsotg) +{ + u32 grxstsr = readl(hsotg->regs + S3C_GRXSTSP); + u32 epnum, status, size; + + WARN_ON(using_dma(hsotg)); + + epnum = grxstsr & S3C_GRXSTS_EPNum_MASK; + status = grxstsr & S3C_GRXSTS_PktSts_MASK; + + size = grxstsr & S3C_GRXSTS_ByteCnt_MASK; + size >>= S3C_GRXSTS_ByteCnt_SHIFT; + + if (1) + dev_dbg(hsotg->dev, "%s: GRXSTSP=0x%08x (%d@%d)\n", + __func__, grxstsr, size, epnum); + +#define __status(x) ((x) >> S3C_GRXSTS_PktSts_SHIFT) + + switch (status >> S3C_GRXSTS_PktSts_SHIFT) { + case __status(S3C_GRXSTS_PktSts_GlobalOutNAK): + dev_dbg(hsotg->dev, "GlobalOutNAK\n"); + break; + + case __status(S3C_GRXSTS_PktSts_OutDone): + dev_dbg(hsotg->dev, "OutDone (Frame=0x%08x)\n", + s3c_hsotg_read_frameno(hsotg)); + + if (!using_dma(hsotg)) + s3c_hsotg_handle_outdone(hsotg, epnum, false); + break; + + case __status(S3C_GRXSTS_PktSts_SetupDone): + dev_dbg(hsotg->dev, + "SetupDone (Frame=0x%08x, DOPEPCTL=0x%08x)\n", + s3c_hsotg_read_frameno(hsotg), + readl(hsotg->regs + S3C_DOEPCTL(0))); + + s3c_hsotg_handle_outdone(hsotg, epnum, true); + break; + + case __status(S3C_GRXSTS_PktSts_OutRX): + s3c_hsotg_rx_data(hsotg, epnum, size); + break; + + case __status(S3C_GRXSTS_PktSts_SetupRX): + dev_dbg(hsotg->dev, + "SetupRX (Frame=0x%08x, DOPEPCTL=0x%08x)\n", + s3c_hsotg_read_frameno(hsotg), + readl(hsotg->regs + S3C_DOEPCTL(0))); + + s3c_hsotg_rx_data(hsotg, epnum, size); + break; + + default: + dev_warn(hsotg->dev, "%s: unknown status %08x\n", + __func__, grxstsr); + + s3c_hsotg_dump(hsotg); + break; + } +} + +/** + * s3c_hsotg_ep0_mps - turn max packet size into register setting + * @mps: The maximum packet size in bytes. +*/ +static u32 s3c_hsotg_ep0_mps(unsigned int mps) +{ + switch (mps) { + case 64: + return S3C_D0EPCTL_MPS_64; + case 32: + return S3C_D0EPCTL_MPS_32; + case 16: + return S3C_D0EPCTL_MPS_16; + case 8: + return S3C_D0EPCTL_MPS_8; + } + + /* bad max packet size, warn and return invalid result */ + WARN_ON(1); + return (u32)-1; +} + +/** + * s3c_hsotg_set_ep_maxpacket - set endpoint's max-packet field + * @hsotg: The driver state. + * @ep: The index number of the endpoint + * @mps: The maximum packet size in bytes + * + * Configure the maximum packet size for the given endpoint, updating + * the hardware control registers to reflect this. + */ +static void s3c_hsotg_set_ep_maxpacket(struct s3c_hsotg *hsotg, + unsigned int ep, unsigned int mps) +{ + struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep]; + void __iomem *regs = hsotg->regs; + u32 mpsval; + u32 reg; + + if (ep == 0) { + /* EP0 is a special case */ + mpsval = s3c_hsotg_ep0_mps(mps); + if (mpsval > 3) + goto bad_mps; + } else { + if (mps >= S3C_DxEPCTL_MPS_LIMIT+1) + goto bad_mps; + + mpsval = mps; + } + + hs_ep->ep.maxpacket = mps; + + /* update both the in and out endpoint controldir_ registers, even + * if one of the directions may not be in use. */ + + reg = readl(regs + S3C_DIEPCTL(ep)); + reg &= ~S3C_DxEPCTL_MPS_MASK; + reg |= mpsval; + writel(reg, regs + S3C_DIEPCTL(ep)); + + reg = readl(regs + S3C_DOEPCTL(ep)); + reg &= ~S3C_DxEPCTL_MPS_MASK; + reg |= mpsval; + writel(reg, regs + S3C_DOEPCTL(ep)); + + return; + +bad_mps: + dev_err(hsotg->dev, "ep%d: bad mps of %d\n", ep, mps); +} + + +/** + * s3c_hsotg_trytx - check to see if anything needs transmitting + * @hsotg: The driver state + * @hs_ep: The driver endpoint to check. + * + * Check to see if there is a request that has data to send, and if so + * make an attempt to write data into the FIFO. + */ +static int s3c_hsotg_trytx(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep) +{ + struct s3c_hsotg_req *hs_req = hs_ep->req; + + if (!hs_ep->dir_in || !hs_req) + return 0; + + if (hs_req->req.actual < hs_req->req.length) { + dev_dbg(hsotg->dev, "trying to write more for ep%d\n", + hs_ep->index); + return s3c_hsotg_write_fifo(hsotg, hs_ep, hs_req); + } + + return 0; +} + +/** + * s3c_hsotg_complete_in - complete IN transfer + * @hsotg: The device state. + * @hs_ep: The endpoint that has just completed. + * + * An IN transfer has been completed, update the transfer's state and then + * call the relevant completion routines. + */ +static void s3c_hsotg_complete_in(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep) +{ + struct s3c_hsotg_req *hs_req = hs_ep->req; + u32 epsize = readl(hsotg->regs + S3C_DIEPTSIZ(hs_ep->index)); + int size_left, size_done; + + if (!hs_req) { + dev_dbg(hsotg->dev, "XferCompl but no req\n"); + return; + } + + /* Calculate the size of the transfer by checking how much is left + * in the endpoint size register and then working it out from + * the amount we loaded for the transfer. + * + * We do this even for DMA, as the transfer may have incremented + * past the end of the buffer (DMA transfers are always 32bit + * aligned). + */ + + size_left = S3C_DxEPTSIZ_XferSize_GET(epsize); + + size_done = hs_ep->size_loaded - size_left; + size_done += hs_ep->last_load; + + if (hs_req->req.actual != size_done) + dev_dbg(hsotg->dev, "%s: adjusting size done %d => %d\n", + __func__, hs_req->req.actual, size_done); + + hs_req->req.actual = size_done; + + /* if we did all of the transfer, and there is more data left + * around, then try restarting the rest of the request */ + + if (!size_left && hs_req->req.actual < hs_req->req.length) { + dev_dbg(hsotg->dev, "%s trying more for req...\n", __func__); + s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); + } else + s3c_hsotg_complete_request_lock(hsotg, hs_ep, hs_req, 0); +} + +/** + * s3c_hsotg_epint - handle an in/out endpoint interrupt + * @hsotg: The driver state + * @idx: The index for the endpoint (0..15) + * @dir_in: Set if this is an IN endpoint + * + * Process and clear any interrupt pending for an individual endpoint +*/ +static void s3c_hsotg_epint(struct s3c_hsotg *hsotg, unsigned int idx, + int dir_in) +{ + struct s3c_hsotg_ep *hs_ep = &hsotg->eps[idx]; + u32 epint_reg = dir_in ? S3C_DIEPINT(idx) : S3C_DOEPINT(idx); + u32 epctl_reg = dir_in ? S3C_DIEPCTL(idx) : S3C_DOEPCTL(idx); + u32 epsiz_reg = dir_in ? S3C_DIEPTSIZ(idx) : S3C_DOEPTSIZ(idx); + u32 ints; + u32 clear = 0; + + ints = readl(hsotg->regs + epint_reg); + + dev_dbg(hsotg->dev, "%s: ep%d(%s) DxEPINT=0x%08x\n", + __func__, idx, dir_in ? "in" : "out", ints); + + if (ints & S3C_DxEPINT_XferCompl) { + dev_dbg(hsotg->dev, + "%s: XferCompl: DxEPCTL=0x%08x, DxEPTSIZ=%08x\n", + __func__, readl(hsotg->regs + epctl_reg), + readl(hsotg->regs + epsiz_reg)); + + /* we get OutDone from the FIFO, so we only need to look + * at completing IN requests here */ + if (dir_in) { + s3c_hsotg_complete_in(hsotg, hs_ep); + + if (idx == 0) + s3c_hsotg_enqueue_setup(hsotg); + } else if (using_dma(hsotg)) { + /* We're using DMA, we need to fire an OutDone here + * as we ignore the RXFIFO. */ + + s3c_hsotg_handle_outdone(hsotg, idx, false); + } + + clear |= S3C_DxEPINT_XferCompl; + } + + if (ints & S3C_DxEPINT_EPDisbld) { + dev_dbg(hsotg->dev, "%s: EPDisbld\n", __func__); + clear |= S3C_DxEPINT_EPDisbld; + } + + if (ints & S3C_DxEPINT_AHBErr) { + dev_dbg(hsotg->dev, "%s: AHBErr\n", __func__); + clear |= S3C_DxEPINT_AHBErr; + } + + if (ints & S3C_DxEPINT_Setup) { /* Setup or Timeout */ + dev_dbg(hsotg->dev, "%s: Setup/Timeout\n", __func__); + + if (using_dma(hsotg) && idx == 0) { + /* this is the notification we've received a + * setup packet. In non-DMA mode we'd get this + * from the RXFIFO, instead we need to process + * the setup here. */ + + if (dir_in) + WARN_ON_ONCE(1); + else + s3c_hsotg_handle_outdone(hsotg, 0, true); + } + + clear |= S3C_DxEPINT_Setup; + } + + if (ints & S3C_DxEPINT_Back2BackSetup) { + dev_dbg(hsotg->dev, "%s: B2BSetup/INEPNakEff\n", __func__); + clear |= S3C_DxEPINT_Back2BackSetup; + } + + if (dir_in) { + /* not sure if this is important, but we'll clear it anyway + */ + if (ints & S3C_DIEPMSK_INTknTXFEmpMsk) { + dev_dbg(hsotg->dev, "%s: ep%d: INTknTXFEmpMsk\n", + __func__, idx); + clear |= S3C_DIEPMSK_INTknTXFEmpMsk; + } + + /* this probably means something bad is happening */ + if (ints & S3C_DIEPMSK_INTknEPMisMsk) { + dev_warn(hsotg->dev, "%s: ep%d: INTknEP\n", + __func__, idx); + clear |= S3C_DIEPMSK_INTknEPMisMsk; + } + } + + writel(clear, hsotg->regs + epint_reg); +} + +/** + * s3c_hsotg_irq_enumdone - Handle EnumDone interrupt (enumeration done) + * @hsotg: The device state. + * + * Handle updating the device settings after the enumeration phase has + * been completed. +*/ +static void s3c_hsotg_irq_enumdone(struct s3c_hsotg *hsotg) +{ + u32 dsts = readl(hsotg->regs + S3C_DSTS); + int ep0_mps = 0, ep_mps; + + /* This should signal the finish of the enumeration phase + * of the USB handshaking, so we should now know what rate + * we connected at. */ + + dev_dbg(hsotg->dev, "EnumDone (DSTS=0x%08x)\n", dsts); + + /* note, since we're limited by the size of transfer on EP0, and + * it seems IN transfers must be a even number of packets we do + * not advertise a 64byte MPS on EP0. */ + + /* catch both EnumSpd_FS and EnumSpd_FS48 */ + switch (dsts & S3C_DSTS_EnumSpd_MASK) { + case S3C_DSTS_EnumSpd_FS: + case S3C_DSTS_EnumSpd_FS48: + hsotg->gadget.speed = USB_SPEED_FULL; + dev_info(hsotg->dev, "new device is full-speed\n"); + + ep0_mps = EP0_MPS_LIMIT; + ep_mps = 64; + break; + + case S3C_DSTS_EnumSpd_HS: + dev_info(hsotg->dev, "new device is high-speed\n"); + hsotg->gadget.speed = USB_SPEED_HIGH; + + ep0_mps = EP0_MPS_LIMIT; + ep_mps = 512; + break; + + case S3C_DSTS_EnumSpd_LS: + hsotg->gadget.speed = USB_SPEED_LOW; + dev_info(hsotg->dev, "new device is low-speed\n"); + + /* note, we don't actually support LS in this driver at the + * moment, and the documentation seems to imply that it isn't + * supported by the PHYs on some of the devices. + */ + break; + } + + /* we should now know the maximum packet size for an + * endpoint, so set the endpoints to a default value. */ + + if (ep0_mps) { + int i; + s3c_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps); + for (i = 1; i < S3C_HSOTG_EPS; i++) + s3c_hsotg_set_ep_maxpacket(hsotg, i, ep_mps); + } + + /* ensure after enumeration our EP0 is active */ + + s3c_hsotg_enqueue_setup(hsotg); + + dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", + readl(hsotg->regs + S3C_DIEPCTL0), + readl(hsotg->regs + S3C_DOEPCTL0)); +} + +/** + * kill_all_requests - remove all requests from the endpoint's queue + * @hsotg: The device state. + * @ep: The endpoint the requests may be on. + * @result: The result code to use. + * @force: Force removal of any current requests + * + * Go through the requests on the given endpoint and mark them + * completed with the given result code. + */ +static void kill_all_requests(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *ep, + int result, bool force) +{ + struct s3c_hsotg_req *req, *treq; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + list_for_each_entry_safe(req, treq, &ep->queue, queue) { + /* currently, we can't do much about an already + * running request on an in endpoint */ + + if (ep->req == req && ep->dir_in && !force) + continue; + + s3c_hsotg_complete_request(hsotg, ep, req, + result); + } + + spin_unlock_irqrestore(&ep->lock, flags); +} + +#define call_gadget(_hs, _entry) \ + if ((_hs)->gadget.speed != USB_SPEED_UNKNOWN && \ + (_hs)->driver && (_hs)->driver->_entry) \ + (_hs)->driver->_entry(&(_hs)->gadget); + +/** + * s3c_hsotg_disconnect_irq - disconnect irq service + * @hsotg: The device state. + * + * A disconnect IRQ has been received, meaning that the host has + * lost contact with the bus. Remove all current transactions + * and signal the gadget driver that this has happened. +*/ +static void s3c_hsotg_disconnect_irq(struct s3c_hsotg *hsotg) +{ + unsigned ep; + + for (ep = 0; ep < S3C_HSOTG_EPS; ep++) + kill_all_requests(hsotg, &hsotg->eps[ep], -ESHUTDOWN, true); + + call_gadget(hsotg, disconnect); +} + +/** + * s3c_hsotg_irq_fifoempty - TX FIFO empty interrupt handler + * @hsotg: The device state: + * @periodic: True if this is a periodic FIFO interrupt + */ +static void s3c_hsotg_irq_fifoempty(struct s3c_hsotg *hsotg, bool periodic) +{ + struct s3c_hsotg_ep *ep; + int epno, ret; + + /* look through for any more data to transmit */ + + for (epno = 0; epno < S3C_HSOTG_EPS; epno++) { + ep = &hsotg->eps[epno]; + + if (!ep->dir_in) + continue; + + if ((periodic && !ep->periodic) || + (!periodic && ep->periodic)) + continue; + + ret = s3c_hsotg_trytx(hsotg, ep); + if (ret < 0) + break; + } +} + +static struct s3c_hsotg *our_hsotg; + +/* IRQ flags which will trigger a retry around the IRQ loop */ +#define IRQ_RETRY_MASK (S3C_GINTSTS_NPTxFEmp | \ + S3C_GINTSTS_PTxFEmp | \ + S3C_GINTSTS_RxFLvl) + +/** + * s3c_hsotg_irq - handle device interrupt + * @irq: The IRQ number triggered + * @pw: The pw value when registered the handler. + */ +static irqreturn_t s3c_hsotg_irq(int irq, void *pw) +{ + struct s3c_hsotg *hsotg = pw; + int retry_count = 8; + u32 gintsts; + u32 gintmsk; + +irq_retry: + gintsts = readl(hsotg->regs + S3C_GINTSTS); + gintmsk = readl(hsotg->regs + S3C_GINTMSK); + + dev_dbg(hsotg->dev, "%s: %08x %08x (%08x) retry %d\n", + __func__, gintsts, gintsts & gintmsk, gintmsk, retry_count); + + gintsts &= gintmsk; + + if (gintsts & S3C_GINTSTS_OTGInt) { + u32 otgint = readl(hsotg->regs + S3C_GOTGINT); + + dev_info(hsotg->dev, "OTGInt: %08x\n", otgint); + + writel(otgint, hsotg->regs + S3C_GOTGINT); + writel(S3C_GINTSTS_OTGInt, hsotg->regs + S3C_GINTSTS); + } + + if (gintsts & S3C_GINTSTS_DisconnInt) { + dev_dbg(hsotg->dev, "%s: DisconnInt\n", __func__); + writel(S3C_GINTSTS_DisconnInt, hsotg->regs + S3C_GINTSTS); + + s3c_hsotg_disconnect_irq(hsotg); + } + + if (gintsts & S3C_GINTSTS_SessReqInt) { + dev_dbg(hsotg->dev, "%s: SessReqInt\n", __func__); + writel(S3C_GINTSTS_SessReqInt, hsotg->regs + S3C_GINTSTS); + } + + if (gintsts & S3C_GINTSTS_EnumDone) { + s3c_hsotg_irq_enumdone(hsotg); + writel(S3C_GINTSTS_EnumDone, hsotg->regs + S3C_GINTSTS); + } + + if (gintsts & S3C_GINTSTS_ConIDStsChng) { + dev_dbg(hsotg->dev, "ConIDStsChg (DSTS=0x%08x, GOTCTL=%08x)\n", + readl(hsotg->regs + S3C_DSTS), + readl(hsotg->regs + S3C_GOTGCTL)); + + writel(S3C_GINTSTS_ConIDStsChng, hsotg->regs + S3C_GINTSTS); + } + + if (gintsts & (S3C_GINTSTS_OEPInt | S3C_GINTSTS_IEPInt)) { + u32 daint = readl(hsotg->regs + S3C_DAINT); + u32 daint_out = daint >> S3C_DAINT_OutEP_SHIFT; + u32 daint_in = daint & ~(daint_out << S3C_DAINT_OutEP_SHIFT); + int ep; + + dev_dbg(hsotg->dev, "%s: daint=%08x\n", __func__, daint); + + for (ep = 0; ep < 15 && daint_out; ep++, daint_out >>= 1) { + if (daint_out & 1) + s3c_hsotg_epint(hsotg, ep, 0); + } + + for (ep = 0; ep < 15 && daint_in; ep++, daint_in >>= 1) { + if (daint_in & 1) + s3c_hsotg_epint(hsotg, ep, 1); + } + + writel(daint, hsotg->regs + S3C_DAINT); + writel(gintsts & (S3C_GINTSTS_OEPInt | S3C_GINTSTS_IEPInt), + hsotg->regs + S3C_GINTSTS); + } + + if (gintsts & S3C_GINTSTS_USBRst) { + dev_info(hsotg->dev, "%s: USBRst\n", __func__); + dev_dbg(hsotg->dev, "GNPTXSTS=%08x\n", + readl(hsotg->regs + S3C_GNPTXSTS)); + + kill_all_requests(hsotg, &hsotg->eps[0], -ECONNRESET, true); + + /* it seems after a reset we can end up with a situation + * where the TXFIFO still has data in it... try flushing + * it to remove anything that may still be in it. + */ + + if (1) { + writel(S3C_GRSTCTL_TxFNum(0) | S3C_GRSTCTL_TxFFlsh, + hsotg->regs + S3C_GRSTCTL); + + dev_info(hsotg->dev, "GNPTXSTS=%08x\n", + readl(hsotg->regs + S3C_GNPTXSTS)); + } + + s3c_hsotg_enqueue_setup(hsotg); + + writel(S3C_GINTSTS_USBRst, hsotg->regs + S3C_GINTSTS); + } + + /* check both FIFOs */ + + if (gintsts & S3C_GINTSTS_NPTxFEmp) { + dev_dbg(hsotg->dev, "NPTxFEmp\n"); + + /* Disable the interrupt to stop it happening again + * unless one of these endpoint routines decides that + * it needs re-enabling */ + + s3c_hsotg_disable_gsint(hsotg, S3C_GINTSTS_NPTxFEmp); + s3c_hsotg_irq_fifoempty(hsotg, false); + + writel(S3C_GINTSTS_NPTxFEmp, hsotg->regs + S3C_GINTSTS); + } + + if (gintsts & S3C_GINTSTS_PTxFEmp) { + dev_dbg(hsotg->dev, "PTxFEmp\n"); + + /* See note in S3C_GINTSTS_NPTxFEmp */ + + s3c_hsotg_disable_gsint(hsotg, S3C_GINTSTS_PTxFEmp); + s3c_hsotg_irq_fifoempty(hsotg, true); + + writel(S3C_GINTSTS_PTxFEmp, hsotg->regs + S3C_GINTSTS); + } + + if (gintsts & S3C_GINTSTS_RxFLvl) { + /* note, since GINTSTS_RxFLvl doubles as FIFO-not-empty, + * we need to retry s3c_hsotg_handle_rx if this is still + * set. */ + + s3c_hsotg_handle_rx(hsotg); + writel(S3C_GINTSTS_RxFLvl, hsotg->regs + S3C_GINTSTS); + } + + if (gintsts & S3C_GINTSTS_ModeMis) { + dev_warn(hsotg->dev, "warning, mode mismatch triggered\n"); + writel(S3C_GINTSTS_ModeMis, hsotg->regs + S3C_GINTSTS); + } + + if (gintsts & S3C_GINTSTS_USBSusp) { + dev_info(hsotg->dev, "S3C_GINTSTS_USBSusp\n"); + writel(S3C_GINTSTS_USBSusp, hsotg->regs + S3C_GINTSTS); + + call_gadget(hsotg, suspend); + } + + if (gintsts & S3C_GINTSTS_WkUpInt) { + dev_info(hsotg->dev, "S3C_GINTSTS_WkUpIn\n"); + writel(S3C_GINTSTS_WkUpInt, hsotg->regs + S3C_GINTSTS); + + call_gadget(hsotg, resume); + } + + if (gintsts & S3C_GINTSTS_ErlySusp) { + dev_dbg(hsotg->dev, "S3C_GINTSTS_ErlySusp\n"); + writel(S3C_GINTSTS_ErlySusp, hsotg->regs + S3C_GINTSTS); + } + + /* these next two seem to crop-up occasionally causing the core + * to shutdown the USB transfer, so try clearing them and logging + * the occurence. */ + + if (gintsts & S3C_GINTSTS_GOUTNakEff) { + dev_info(hsotg->dev, "GOUTNakEff triggered\n"); + + s3c_hsotg_dump(hsotg); + + writel(S3C_DCTL_CGOUTNak, hsotg->regs + S3C_DCTL); + writel(S3C_GINTSTS_GOUTNakEff, hsotg->regs + S3C_GINTSTS); + } + + if (gintsts & S3C_GINTSTS_GINNakEff) { + dev_info(hsotg->dev, "GINNakEff triggered\n"); + + s3c_hsotg_dump(hsotg); + + writel(S3C_DCTL_CGNPInNAK, hsotg->regs + S3C_DCTL); + writel(S3C_GINTSTS_GINNakEff, hsotg->regs + S3C_GINTSTS); + } + + /* if we've had fifo events, we should try and go around the + * loop again to see if there's any point in returning yet. */ + + if (gintsts & IRQ_RETRY_MASK && --retry_count > 0) + goto irq_retry; + + return IRQ_HANDLED; +} + +/** + * s3c_hsotg_ep_enable - enable the given endpoint + * @ep: The USB endpint to configure + * @desc: The USB endpoint descriptor to configure with. + * + * This is called from the USB gadget code's usb_ep_enable(). +*/ +static int s3c_hsotg_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hsotg = hs_ep->parent; + unsigned long flags; + int index = hs_ep->index; + u32 epctrl_reg; + u32 epctrl; + u32 mps; + int dir_in; + + dev_dbg(hsotg->dev, + "%s: ep %s: a 0x%02x, attr 0x%02x, mps 0x%04x, intr %d\n", + __func__, ep->name, desc->bEndpointAddress, desc->bmAttributes, + desc->wMaxPacketSize, desc->bInterval); + + /* not to be called for EP0 */ + WARN_ON(index == 0); + + dir_in = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? 1 : 0; + if (dir_in != hs_ep->dir_in) { + dev_err(hsotg->dev, "%s: direction mismatch!\n", __func__); + return -EINVAL; + } + + mps = le16_to_cpu(desc->wMaxPacketSize); + + /* note, we handle this here instead of s3c_hsotg_set_ep_maxpacket */ + + epctrl_reg = dir_in ? S3C_DIEPCTL(index) : S3C_DOEPCTL(index); + epctrl = readl(hsotg->regs + epctrl_reg); + + dev_dbg(hsotg->dev, "%s: read DxEPCTL=0x%08x from 0x%08x\n", + __func__, epctrl, epctrl_reg); + + spin_lock_irqsave(&hs_ep->lock, flags); + + epctrl &= ~(S3C_DxEPCTL_EPType_MASK | S3C_DxEPCTL_MPS_MASK); + epctrl |= S3C_DxEPCTL_MPS(mps); + + /* mark the endpoint as active, otherwise the core may ignore + * transactions entirely for this endpoint */ + epctrl |= S3C_DxEPCTL_USBActEp; + + /* set the NAK status on the endpoint, otherwise we might try and + * do something with data that we've yet got a request to process + * since the RXFIFO will take data for an endpoint even if the + * size register hasn't been set. + */ + + epctrl |= S3C_DxEPCTL_SNAK; + + /* update the endpoint state */ + hs_ep->ep.maxpacket = mps; + + /* default, set to non-periodic */ + hs_ep->periodic = 0; + + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_ISOC: + dev_err(hsotg->dev, "no current ISOC support\n"); + return -EINVAL; + + case USB_ENDPOINT_XFER_BULK: + epctrl |= S3C_DxEPCTL_EPType_Bulk; + break; + + case USB_ENDPOINT_XFER_INT: + if (dir_in) { + /* Allocate our TxFNum by simply using the index + * of the endpoint for the moment. We could do + * something better if the host indicates how + * many FIFOs we are expecting to use. */ + + hs_ep->periodic = 1; + epctrl |= S3C_DxEPCTL_TxFNum(index); + } + + epctrl |= S3C_DxEPCTL_EPType_Intterupt; + break; + + case USB_ENDPOINT_XFER_CONTROL: + epctrl |= S3C_DxEPCTL_EPType_Control; + break; + } + + /* for non control endpoints, set PID to D0 */ + if (index) + epctrl |= S3C_DxEPCTL_SetD0PID; + + dev_dbg(hsotg->dev, "%s: write DxEPCTL=0x%08x\n", + __func__, epctrl); + + writel(epctrl, hsotg->regs + epctrl_reg); + dev_dbg(hsotg->dev, "%s: read DxEPCTL=0x%08x\n", + __func__, readl(hsotg->regs + epctrl_reg)); + + /* enable the endpoint interrupt */ + s3c_hsotg_ctrl_epint(hsotg, index, dir_in, 1); + + spin_unlock_irqrestore(&hs_ep->lock, flags); + return 0; +} + +static int s3c_hsotg_ep_disable(struct usb_ep *ep) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hsotg = hs_ep->parent; + int dir_in = hs_ep->dir_in; + int index = hs_ep->index; + unsigned long flags; + u32 epctrl_reg; + u32 ctrl; + + dev_info(hsotg->dev, "%s(ep %p)\n", __func__, ep); + + if (ep == &hsotg->eps[0].ep) { + dev_err(hsotg->dev, "%s: called for ep0\n", __func__); + return -EINVAL; + } + + epctrl_reg = dir_in ? S3C_DIEPCTL(index) : S3C_DOEPCTL(index); + + /* terminate all requests with shutdown */ + kill_all_requests(hsotg, hs_ep, -ESHUTDOWN, false); + + spin_lock_irqsave(&hs_ep->lock, flags); + + ctrl = readl(hsotg->regs + epctrl_reg); + ctrl &= ~S3C_DxEPCTL_EPEna; + ctrl &= ~S3C_DxEPCTL_USBActEp; + ctrl |= S3C_DxEPCTL_SNAK; + + dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); + writel(ctrl, hsotg->regs + epctrl_reg); + + /* disable endpoint interrupts */ + s3c_hsotg_ctrl_epint(hsotg, hs_ep->index, hs_ep->dir_in, 0); + + spin_unlock_irqrestore(&hs_ep->lock, flags); + return 0; +} + +/** + * on_list - check request is on the given endpoint + * @ep: The endpoint to check. + * @test: The request to test if it is on the endpoint. +*/ +static bool on_list(struct s3c_hsotg_ep *ep, struct s3c_hsotg_req *test) +{ + struct s3c_hsotg_req *req, *treq; + + list_for_each_entry_safe(req, treq, &ep->queue, queue) { + if (req == test) + return true; + } + + return false; +} + +static int s3c_hsotg_ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct s3c_hsotg_req *hs_req = our_req(req); + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hs = hs_ep->parent; + unsigned long flags; + + dev_info(hs->dev, "ep_dequeue(%p,%p)\n", ep, req); + + if (hs_req == hs_ep->req) { + dev_dbg(hs->dev, "%s: already in progress\n", __func__); + return -EINPROGRESS; + } + + spin_lock_irqsave(&hs_ep->lock, flags); + + if (!on_list(hs_ep, hs_req)) { + spin_unlock_irqrestore(&hs_ep->lock, flags); + return -EINVAL; + } + + s3c_hsotg_complete_request(hs, hs_ep, hs_req, -ECONNRESET); + spin_unlock_irqrestore(&hs_ep->lock, flags); + + return 0; +} + +static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value) +{ + struct s3c_hsotg_ep *hs_ep = our_ep(ep); + struct s3c_hsotg *hs = hs_ep->parent; + int index = hs_ep->index; + unsigned long irqflags; + u32 epreg; + u32 epctl; + + dev_info(hs->dev, "%s(ep %p %s, %d)\n", __func__, ep, ep->name, value); + + spin_lock_irqsave(&hs_ep->lock, irqflags); + + /* write both IN and OUT control registers */ + + epreg = S3C_DIEPCTL(index); + epctl = readl(hs->regs + epreg); + + if (value) + epctl |= S3C_DxEPCTL_Stall; + else + epctl &= ~S3C_DxEPCTL_Stall; + + writel(epctl, hs->regs + epreg); + + epreg = S3C_DOEPCTL(index); + epctl = readl(hs->regs + epreg); + + if (value) + epctl |= S3C_DxEPCTL_Stall; + else + epctl &= ~S3C_DxEPCTL_Stall; + + writel(epctl, hs->regs + epreg); + + spin_unlock_irqrestore(&hs_ep->lock, irqflags); + + return 0; +} + +static struct usb_ep_ops s3c_hsotg_ep_ops = { + .enable = s3c_hsotg_ep_enable, + .disable = s3c_hsotg_ep_disable, + .alloc_request = s3c_hsotg_ep_alloc_request, + .free_request = s3c_hsotg_ep_free_request, + .queue = s3c_hsotg_ep_queue, + .dequeue = s3c_hsotg_ep_dequeue, + .set_halt = s3c_hsotg_ep_sethalt, + /* note, don't belive we have any call for the fifo routines */ +}; + +/** + * s3c_hsotg_corereset - issue softreset to the core + * @hsotg: The device state + * + * Issue a soft reset to the core, and await the core finishing it. +*/ +static int s3c_hsotg_corereset(struct s3c_hsotg *hsotg) +{ + int timeout; + u32 grstctl; + + dev_dbg(hsotg->dev, "resetting core\n"); + + /* issue soft reset */ + writel(S3C_GRSTCTL_CSftRst, hsotg->regs + S3C_GRSTCTL); + + timeout = 1000; + do { + grstctl = readl(hsotg->regs + S3C_GRSTCTL); + } while (!(grstctl & S3C_GRSTCTL_CSftRst) && timeout-- > 0); + + if (!grstctl & S3C_GRSTCTL_CSftRst) { + dev_err(hsotg->dev, "Failed to get CSftRst asserted\n"); + return -EINVAL; + } + + timeout = 1000; + + while (1) { + u32 grstctl = readl(hsotg->regs + S3C_GRSTCTL); + + if (timeout-- < 0) { + dev_info(hsotg->dev, + "%s: reset failed, GRSTCTL=%08x\n", + __func__, grstctl); + return -ETIMEDOUT; + } + + if (grstctl & S3C_GRSTCTL_CSftRst) + continue; + + if (!(grstctl & S3C_GRSTCTL_AHBIdle)) + continue; + + break; /* reset done */ + } + + dev_dbg(hsotg->dev, "reset successful\n"); + return 0; +} + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct s3c_hsotg *hsotg = our_hsotg; + int ret; + + if (!hsotg) { + printk(KERN_ERR "%s: called with no device\n", __func__); + return -ENODEV; + } + + if (!driver) { + dev_err(hsotg->dev, "%s: no driver\n", __func__); + return -EINVAL; + } + + if (driver->speed != USB_SPEED_HIGH && + driver->speed != USB_SPEED_FULL) { + dev_err(hsotg->dev, "%s: bad speed\n", __func__); + } + + if (!driver->bind || !driver->setup) { + dev_err(hsotg->dev, "%s: missing entry points\n", __func__); + return -EINVAL; + } + + WARN_ON(hsotg->driver); + + driver->driver.bus = NULL; + hsotg->driver = driver; + hsotg->gadget.dev.driver = &driver->driver; + hsotg->gadget.dev.dma_mask = hsotg->dev->dma_mask; + hsotg->gadget.speed = USB_SPEED_UNKNOWN; + + ret = device_add(&hsotg->gadget.dev); + if (ret) { + dev_err(hsotg->dev, "failed to register gadget device\n"); + goto err; + } + + ret = driver->bind(&hsotg->gadget); + if (ret) { + dev_err(hsotg->dev, "failed bind %s\n", driver->driver.name); + + hsotg->gadget.dev.driver = NULL; + hsotg->driver = NULL; + goto err; + } + + /* we must now enable ep0 ready for host detection and then + * set configuration. */ + + s3c_hsotg_corereset(hsotg); + + /* set the PLL on, remove the HNP/SRP and set the PHY */ + writel(S3C_GUSBCFG_PHYIf16 | S3C_GUSBCFG_TOutCal(7) | + (0x5 << 10), hsotg->regs + S3C_GUSBCFG); + + /* looks like soft-reset changes state of FIFOs */ + s3c_hsotg_init_fifo(hsotg); + + __orr32(hsotg->regs + S3C_DCTL, S3C_DCTL_SftDiscon); + + writel(1 << 18 | S3C_DCFG_DevSpd_HS, hsotg->regs + S3C_DCFG); + + writel(S3C_GINTSTS_DisconnInt | S3C_GINTSTS_SessReqInt | + S3C_GINTSTS_ConIDStsChng | S3C_GINTSTS_USBRst | + S3C_GINTSTS_EnumDone | S3C_GINTSTS_OTGInt | + S3C_GINTSTS_USBSusp | S3C_GINTSTS_WkUpInt | + S3C_GINTSTS_GOUTNakEff | S3C_GINTSTS_GINNakEff | + S3C_GINTSTS_ErlySusp, + hsotg->regs + S3C_GINTMSK); + + if (using_dma(hsotg)) + writel(S3C_GAHBCFG_GlblIntrEn | S3C_GAHBCFG_DMAEn | + S3C_GAHBCFG_HBstLen_Incr4, + hsotg->regs + S3C_GAHBCFG); + else + writel(S3C_GAHBCFG_GlblIntrEn, hsotg->regs + S3C_GAHBCFG); + + /* Enabling INTknTXFEmpMsk here seems to be a big mistake, we end + * up being flooded with interrupts if the host is polling the + * endpoint to try and read data. */ + + writel(S3C_DIEPMSK_TimeOUTMsk | S3C_DIEPMSK_AHBErrMsk | + S3C_DIEPMSK_INTknEPMisMsk | + S3C_DIEPMSK_EPDisbldMsk | S3C_DIEPMSK_XferComplMsk, + hsotg->regs + S3C_DIEPMSK); + + /* don't need XferCompl, we get that from RXFIFO in slave mode. In + * DMA mode we may need this. */ + writel(S3C_DOEPMSK_SetupMsk | S3C_DOEPMSK_AHBErrMsk | + S3C_DOEPMSK_EPDisbldMsk | + using_dma(hsotg) ? (S3C_DIEPMSK_XferComplMsk | + S3C_DIEPMSK_TimeOUTMsk) : 0, + hsotg->regs + S3C_DOEPMSK); + + writel(0, hsotg->regs + S3C_DAINTMSK); + + dev_info(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", + readl(hsotg->regs + S3C_DIEPCTL0), + readl(hsotg->regs + S3C_DOEPCTL0)); + + /* enable in and out endpoint interrupts */ + s3c_hsotg_en_gsint(hsotg, S3C_GINTSTS_OEPInt | S3C_GINTSTS_IEPInt); + + /* Enable the RXFIFO when in slave mode, as this is how we collect + * the data. In DMA mode, we get events from the FIFO but also + * things we cannot process, so do not use it. */ + if (!using_dma(hsotg)) + s3c_hsotg_en_gsint(hsotg, S3C_GINTSTS_RxFLvl); + + /* Enable interrupts for EP0 in and out */ + s3c_hsotg_ctrl_epint(hsotg, 0, 0, 1); + s3c_hsotg_ctrl_epint(hsotg, 0, 1, 1); + + __orr32(hsotg->regs + S3C_DCTL, S3C_DCTL_PWROnPrgDone); + udelay(10); /* see openiboot */ + __bic32(hsotg->regs + S3C_DCTL, S3C_DCTL_PWROnPrgDone); + + dev_info(hsotg->dev, "DCTL=0x%08x\n", readl(hsotg->regs + S3C_DCTL)); + + /* S3C_DxEPCTL_USBActEp says RO in manual, but seems to be set by + writing to the EPCTL register.. */ + + /* set to read 1 8byte packet */ + writel(S3C_DxEPTSIZ_MC(1) | S3C_DxEPTSIZ_PktCnt(1) | + S3C_DxEPTSIZ_XferSize(8), hsotg->regs + DOEPTSIZ0); + + writel(s3c_hsotg_ep0_mps(hsotg->eps[0].ep.maxpacket) | + S3C_DxEPCTL_CNAK | S3C_DxEPCTL_EPEna | + S3C_DxEPCTL_USBActEp, + hsotg->regs + S3C_DOEPCTL0); + + /* enable, but don't activate EP0in */ + writel(s3c_hsotg_ep0_mps(hsotg->eps[0].ep.maxpacket) | + S3C_DxEPCTL_USBActEp, hsotg->regs + S3C_DIEPCTL0); + + s3c_hsotg_enqueue_setup(hsotg); + + dev_info(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", + readl(hsotg->regs + S3C_DIEPCTL0), + readl(hsotg->regs + S3C_DOEPCTL0)); + + /* clear global NAKs */ + writel(S3C_DCTL_CGOUTNak | S3C_DCTL_CGNPInNAK, + hsotg->regs + S3C_DCTL); + + /* remove the soft-disconnect and let's go */ + __bic32(hsotg->regs + S3C_DCTL, S3C_DCTL_SftDiscon); + + /* report to the user, and return */ + + dev_info(hsotg->dev, "bound driver %s\n", driver->driver.name); + return 0; + +err: + hsotg->driver = NULL; + hsotg->gadget.dev.driver = NULL; + return ret; +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct s3c_hsotg *hsotg = our_hsotg; + int ep; + + if (!hsotg) + return -ENODEV; + + if (!driver || driver != hsotg->driver || !driver->unbind) + return -EINVAL; + + /* all endpoints should be shutdown */ + for (ep = 0; ep < S3C_HSOTG_EPS; ep++) + s3c_hsotg_ep_disable(&hsotg->eps[ep].ep); + + call_gadget(hsotg, disconnect); + + driver->unbind(&hsotg->gadget); + hsotg->driver = NULL; + hsotg->gadget.speed = USB_SPEED_UNKNOWN; + + device_del(&hsotg->gadget.dev); + + dev_info(hsotg->dev, "unregistered gadget driver '%s'\n", + driver->driver.name); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +static int s3c_hsotg_gadget_getframe(struct usb_gadget *gadget) +{ + return s3c_hsotg_read_frameno(to_hsotg(gadget)); +} + +static struct usb_gadget_ops s3c_hsotg_gadget_ops = { + .get_frame = s3c_hsotg_gadget_getframe, +}; + +/** + * s3c_hsotg_initep - initialise a single endpoint + * @hsotg: The device state. + * @hs_ep: The endpoint to be initialised. + * @epnum: The endpoint number + * + * Initialise the given endpoint (as part of the probe and device state + * creation) to give to the gadget driver. Setup the endpoint name, any + * direction information and other state that may be required. + */ +static void __devinit s3c_hsotg_initep(struct s3c_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, + int epnum) +{ + u32 ptxfifo; + char *dir; + + if (epnum == 0) + dir = ""; + else if ((epnum % 2) == 0) { + dir = "out"; + } else { + dir = "in"; + hs_ep->dir_in = 1; + } + + hs_ep->index = epnum; + + snprintf(hs_ep->name, sizeof(hs_ep->name), "ep%d%s", epnum, dir); + + INIT_LIST_HEAD(&hs_ep->queue); + INIT_LIST_HEAD(&hs_ep->ep.ep_list); + + spin_lock_init(&hs_ep->lock); + + /* add to the list of endpoints known by the gadget driver */ + if (epnum) + list_add_tail(&hs_ep->ep.ep_list, &hsotg->gadget.ep_list); + + hs_ep->parent = hsotg; + hs_ep->ep.name = hs_ep->name; + hs_ep->ep.maxpacket = epnum ? 512 : EP0_MPS_LIMIT; + hs_ep->ep.ops = &s3c_hsotg_ep_ops; + + /* Read the FIFO size for the Periodic TX FIFO, even if we're + * an OUT endpoint, we may as well do this if in future the + * code is changed to make each endpoint's direction changeable. + */ + + ptxfifo = readl(hsotg->regs + S3C_DPTXFSIZn(epnum)); + hs_ep->fifo_size = S3C_DPTXFSIZn_DPTxFSize_GET(ptxfifo); + + /* if we're using dma, we need to set the next-endpoint pointer + * to be something valid. + */ + + if (using_dma(hsotg)) { + u32 next = S3C_DxEPCTL_NextEp((epnum + 1) % 15); + writel(next, hsotg->regs + S3C_DIEPCTL(epnum)); + writel(next, hsotg->regs + S3C_DOEPCTL(epnum)); + } +} + +/** + * s3c_hsotg_otgreset - reset the OtG phy block + * @hsotg: The host state. + * + * Power up the phy, set the basic configuration and start the PHY. + */ +static void s3c_hsotg_otgreset(struct s3c_hsotg *hsotg) +{ + u32 osc; + + writel(0, S3C_PHYPWR); + mdelay(1); + + osc = hsotg->plat->is_osc ? S3C_PHYCLK_EXT_OSC : 0; + + writel(osc | 0x10, S3C_PHYCLK); + + /* issue a full set of resets to the otg and core */ + + writel(S3C_RSTCON_PHY, S3C_RSTCON); + udelay(20); /* at-least 10uS */ + writel(0, S3C_RSTCON); +} + + +static void s3c_hsotg_init(struct s3c_hsotg *hsotg) +{ + /* unmask subset of endpoint interrupts */ + + writel(S3C_DIEPMSK_TimeOUTMsk | S3C_DIEPMSK_AHBErrMsk | + S3C_DIEPMSK_EPDisbldMsk | S3C_DIEPMSK_XferComplMsk, + hsotg->regs + S3C_DIEPMSK); + + writel(S3C_DOEPMSK_SetupMsk | S3C_DOEPMSK_AHBErrMsk | + S3C_DOEPMSK_EPDisbldMsk | S3C_DOEPMSK_XferComplMsk, + hsotg->regs + S3C_DOEPMSK); + + writel(0, hsotg->regs + S3C_DAINTMSK); + + if (0) { + /* post global nak until we're ready */ + writel(S3C_DCTL_SGNPInNAK | S3C_DCTL_SGOUTNak, + hsotg->regs + S3C_DCTL); + } + + /* setup fifos */ + + dev_info(hsotg->dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", + readl(hsotg->regs + S3C_GRXFSIZ), + readl(hsotg->regs + S3C_GNPTXFSIZ)); + + s3c_hsotg_init_fifo(hsotg); + + /* set the PLL on, remove the HNP/SRP and set the PHY */ + writel(S3C_GUSBCFG_PHYIf16 | S3C_GUSBCFG_TOutCal(7) | (0x5 << 10), + hsotg->regs + S3C_GUSBCFG); + + writel(using_dma(hsotg) ? S3C_GAHBCFG_DMAEn : 0x0, + hsotg->regs + S3C_GAHBCFG); +} + +static void s3c_hsotg_dump(struct s3c_hsotg *hsotg) +{ + struct device *dev = hsotg->dev; + void __iomem *regs = hsotg->regs; + u32 val; + int idx; + + dev_info(dev, "DCFG=0x%08x, DCTL=0x%08x, DIEPMSK=%08x\n", + readl(regs + S3C_DCFG), readl(regs + S3C_DCTL), + readl(regs + S3C_DIEPMSK)); + + dev_info(dev, "GAHBCFG=0x%08x, 0x44=0x%08x\n", + readl(regs + S3C_GAHBCFG), readl(regs + 0x44)); + + dev_info(dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", + readl(regs + S3C_GRXFSIZ), readl(regs + S3C_GNPTXFSIZ)); + + /* show periodic fifo settings */ + + for (idx = 1; idx <= 15; idx++) { + val = readl(regs + S3C_DPTXFSIZn(idx)); + dev_info(dev, "DPTx[%d] FSize=%d, StAddr=0x%08x\n", idx, + val >> S3C_DPTXFSIZn_DPTxFSize_SHIFT, + val & S3C_DPTXFSIZn_DPTxFStAddr_MASK); + } + + for (idx = 0; idx < 15; idx++) { + dev_info(dev, + "ep%d-in: EPCTL=0x%08x, SIZ=0x%08x, DMA=0x%08x\n", idx, + readl(regs + S3C_DIEPCTL(idx)), + readl(regs + S3C_DIEPTSIZ(idx)), + readl(regs + S3C_DIEPDMA(idx))); + + val = readl(regs + S3C_DOEPCTL(idx)); + dev_info(dev, + "ep%d-out: EPCTL=0x%08x, SIZ=0x%08x, DMA=0x%08x\n", + idx, readl(regs + S3C_DOEPCTL(idx)), + readl(regs + S3C_DOEPTSIZ(idx)), + readl(regs + S3C_DOEPDMA(idx))); + + } + + dev_info(dev, "DVBUSDIS=0x%08x, DVBUSPULSE=%08x\n", + readl(regs + S3C_DVBUSDIS), readl(regs + S3C_DVBUSPULSE)); +} + + +/** + * state_show - debugfs: show overall driver and device state. + * @seq: The seq file to write to. + * @v: Unused parameter. + * + * This debugfs entry shows the overall state of the hardware and + * some general information about each of the endpoints available + * to the system. + */ +static int state_show(struct seq_file *seq, void *v) +{ + struct s3c_hsotg *hsotg = seq->private; + void __iomem *regs = hsotg->regs; + int idx; + + seq_printf(seq, "DCFG=0x%08x, DCTL=0x%08x, DSTS=0x%08x\n", + readl(regs + S3C_DCFG), + readl(regs + S3C_DCTL), + readl(regs + S3C_DSTS)); + + seq_printf(seq, "DIEPMSK=0x%08x, DOEPMASK=0x%08x\n", + readl(regs + S3C_DIEPMSK), readl(regs + S3C_DOEPMSK)); + + seq_printf(seq, "GINTMSK=0x%08x, GINTSTS=0x%08x\n", + readl(regs + S3C_GINTMSK), + readl(regs + S3C_GINTSTS)); + + seq_printf(seq, "DAINTMSK=0x%08x, DAINT=0x%08x\n", + readl(regs + S3C_DAINTMSK), + readl(regs + S3C_DAINT)); + + seq_printf(seq, "GNPTXSTS=0x%08x, GRXSTSR=%08x\n", + readl(regs + S3C_GNPTXSTS), + readl(regs + S3C_GRXSTSR)); + + seq_printf(seq, "\nEndpoint status:\n"); + + for (idx = 0; idx < 15; idx++) { + u32 in, out; + + in = readl(regs + S3C_DIEPCTL(idx)); + out = readl(regs + S3C_DOEPCTL(idx)); + + seq_printf(seq, "ep%d: DIEPCTL=0x%08x, DOEPCTL=0x%08x", + idx, in, out); + + in = readl(regs + S3C_DIEPTSIZ(idx)); + out = readl(regs + S3C_DOEPTSIZ(idx)); + + seq_printf(seq, ", DIEPTSIZ=0x%08x, DOEPTSIZ=0x%08x", + in, out); + + seq_printf(seq, "\n"); + } + + return 0; +} + +static int state_open(struct inode *inode, struct file *file) +{ + return single_open(file, state_show, inode->i_private); +} + +static const struct file_operations state_fops = { + .owner = THIS_MODULE, + .open = state_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * fifo_show - debugfs: show the fifo information + * @seq: The seq_file to write data to. + * @v: Unused parameter. + * + * Show the FIFO information for the overall fifo and all the + * periodic transmission FIFOs. +*/ +static int fifo_show(struct seq_file *seq, void *v) +{ + struct s3c_hsotg *hsotg = seq->private; + void __iomem *regs = hsotg->regs; + u32 val; + int idx; + + seq_printf(seq, "Non-periodic FIFOs:\n"); + seq_printf(seq, "RXFIFO: Size %d\n", readl(regs + S3C_GRXFSIZ)); + + val = readl(regs + S3C_GNPTXFSIZ); + seq_printf(seq, "NPTXFIFO: Size %d, Start 0x%08x\n", + val >> S3C_GNPTXFSIZ_NPTxFDep_SHIFT, + val & S3C_GNPTXFSIZ_NPTxFStAddr_MASK); + + seq_printf(seq, "\nPeriodic TXFIFOs:\n"); + + for (idx = 1; idx <= 15; idx++) { + val = readl(regs + S3C_DPTXFSIZn(idx)); + + seq_printf(seq, "\tDPTXFIFO%2d: Size %d, Start 0x%08x\n", idx, + val >> S3C_DPTXFSIZn_DPTxFSize_SHIFT, + val & S3C_DPTXFSIZn_DPTxFStAddr_MASK); + } + + return 0; +} + +static int fifo_open(struct inode *inode, struct file *file) +{ + return single_open(file, fifo_show, inode->i_private); +} + +static const struct file_operations fifo_fops = { + .owner = THIS_MODULE, + .open = fifo_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + + +static const char *decode_direction(int is_in) +{ + return is_in ? "in" : "out"; +} + +/** + * ep_show - debugfs: show the state of an endpoint. + * @seq: The seq_file to write data to. + * @v: Unused parameter. + * + * This debugfs entry shows the state of the given endpoint (one is + * registered for each available). +*/ +static int ep_show(struct seq_file *seq, void *v) +{ + struct s3c_hsotg_ep *ep = seq->private; + struct s3c_hsotg *hsotg = ep->parent; + struct s3c_hsotg_req *req; + void __iomem *regs = hsotg->regs; + int index = ep->index; + int show_limit = 15; + unsigned long flags; + + seq_printf(seq, "Endpoint index %d, named %s, dir %s:\n", + ep->index, ep->ep.name, decode_direction(ep->dir_in)); + + /* first show the register state */ + + seq_printf(seq, "\tDIEPCTL=0x%08x, DOEPCTL=0x%08x\n", + readl(regs + S3C_DIEPCTL(index)), + readl(regs + S3C_DOEPCTL(index))); + + seq_printf(seq, "\tDIEPDMA=0x%08x, DOEPDMA=0x%08x\n", + readl(regs + S3C_DIEPDMA(index)), + readl(regs + S3C_DOEPDMA(index))); + + seq_printf(seq, "\tDIEPINT=0x%08x, DOEPINT=0x%08x\n", + readl(regs + S3C_DIEPINT(index)), + readl(regs + S3C_DOEPINT(index))); + + seq_printf(seq, "\tDIEPTSIZ=0x%08x, DOEPTSIZ=0x%08x\n", + readl(regs + S3C_DIEPTSIZ(index)), + readl(regs + S3C_DOEPTSIZ(index))); + + seq_printf(seq, "\n"); + seq_printf(seq, "mps %d\n", ep->ep.maxpacket); + seq_printf(seq, "total_data=%ld\n", ep->total_data); + + seq_printf(seq, "request list (%p,%p):\n", + ep->queue.next, ep->queue.prev); + + spin_lock_irqsave(&ep->lock, flags); + + list_for_each_entry(req, &ep->queue, queue) { + if (--show_limit < 0) { + seq_printf(seq, "not showing more requests...\n"); + break; + } + + seq_printf(seq, "%c req %p: %d bytes @%p, ", + req == ep->req ? '*' : ' ', + req, req->req.length, req->req.buf); + seq_printf(seq, "%d done, res %d\n", + req->req.actual, req->req.status); + } + + spin_unlock_irqrestore(&ep->lock, flags); + + return 0; +} + +static int ep_open(struct inode *inode, struct file *file) +{ + return single_open(file, ep_show, inode->i_private); +} + +static const struct file_operations ep_fops = { + .owner = THIS_MODULE, + .open = ep_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * s3c_hsotg_create_debug - create debugfs directory and files + * @hsotg: The driver state + * + * Create the debugfs files to allow the user to get information + * about the state of the system. The directory name is created + * with the same name as the device itself, in case we end up + * with multiple blocks in future systems. +*/ +static void __devinit s3c_hsotg_create_debug(struct s3c_hsotg *hsotg) +{ + struct dentry *root; + unsigned epidx; + + root = debugfs_create_dir(dev_name(hsotg->dev), NULL); + hsotg->debug_root = root; + if (IS_ERR(root)) { + dev_err(hsotg->dev, "cannot create debug root\n"); + return; + } + + /* create general state file */ + + hsotg->debug_file = debugfs_create_file("state", 0444, root, + hsotg, &state_fops); + + if (IS_ERR(hsotg->debug_file)) + dev_err(hsotg->dev, "%s: failed to create state\n", __func__); + + hsotg->debug_fifo = debugfs_create_file("fifo", 0444, root, + hsotg, &fifo_fops); + + if (IS_ERR(hsotg->debug_fifo)) + dev_err(hsotg->dev, "%s: failed to create fifo\n", __func__); + + /* create one file for each endpoint */ + + for (epidx = 0; epidx < S3C_HSOTG_EPS; epidx++) { + struct s3c_hsotg_ep *ep = &hsotg->eps[epidx]; + + ep->debugfs = debugfs_create_file(ep->name, 0444, + root, ep, &ep_fops); + + if (IS_ERR(ep->debugfs)) + dev_err(hsotg->dev, "failed to create %s debug file\n", + ep->name); + } +} + +/** + * s3c_hsotg_delete_debug - cleanup debugfs entries + * @hsotg: The driver state + * + * Cleanup (remove) the debugfs files for use on module exit. +*/ +static void __devexit s3c_hsotg_delete_debug(struct s3c_hsotg *hsotg) +{ + unsigned epidx; + + for (epidx = 0; epidx < S3C_HSOTG_EPS; epidx++) { + struct s3c_hsotg_ep *ep = &hsotg->eps[epidx]; + debugfs_remove(ep->debugfs); + } + + debugfs_remove(hsotg->debug_file); + debugfs_remove(hsotg->debug_fifo); + debugfs_remove(hsotg->debug_root); +} + +/** + * s3c_hsotg_gate - set the hardware gate for the block + * @pdev: The device we bound to + * @on: On or off. + * + * Set the hardware gate setting into the block. If we end up on + * something other than an S3C64XX, then we might need to change this + * to using a platform data callback, or some other mechanism. + */ +static void s3c_hsotg_gate(struct platform_device *pdev, bool on) +{ + unsigned long flags; + u32 others; + + local_irq_save(flags); + + others = __raw_readl(S3C64XX_OTHERS); + if (on) + others |= S3C64XX_OTHERS_USBMASK; + else + others &= ~S3C64XX_OTHERS_USBMASK; + __raw_writel(others, S3C64XX_OTHERS); + + local_irq_restore(flags); +} + +struct s3c_hsotg_plat s3c_hsotg_default_pdata; + +static int __devinit s3c_hsotg_probe(struct platform_device *pdev) +{ + struct s3c_hsotg_plat *plat = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct s3c_hsotg *hsotg; + struct resource *res; + int epnum; + int ret; + + if (!plat) + plat = &s3c_hsotg_default_pdata; + + hsotg = kzalloc(sizeof(struct s3c_hsotg) + + sizeof(struct s3c_hsotg_ep) * S3C_HSOTG_EPS, + GFP_KERNEL); + if (!hsotg) { + dev_err(dev, "cannot get memory\n"); + return -ENOMEM; + } + + hsotg->dev = dev; + hsotg->plat = plat; + + platform_set_drvdata(pdev, hsotg); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "cannot find register resource 0\n"); + ret = -EINVAL; + goto err_mem; + } + + hsotg->regs_res = request_mem_region(res->start, resource_size(res), + dev_name(dev)); + if (!hsotg->regs_res) { + dev_err(dev, "cannot reserve registers\n"); + ret = -ENOENT; + goto err_mem; + } + + hsotg->regs = ioremap(res->start, resource_size(res)); + if (!hsotg->regs) { + dev_err(dev, "cannot map registers\n"); + ret = -ENXIO; + goto err_regs_res; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(dev, "cannot find IRQ\n"); + goto err_regs; + } + + hsotg->irq = ret; + + ret = request_irq(ret, s3c_hsotg_irq, 0, dev_name(dev), hsotg); + if (ret < 0) { + dev_err(dev, "cannot claim IRQ\n"); + goto err_regs; + } + + dev_info(dev, "regs %p, irq %d\n", hsotg->regs, hsotg->irq); + + device_initialize(&hsotg->gadget.dev); + + dev_set_name(&hsotg->gadget.dev, "gadget"); + + hsotg->gadget.is_dualspeed = 1; + hsotg->gadget.ops = &s3c_hsotg_gadget_ops; + hsotg->gadget.name = dev_name(dev); + + hsotg->gadget.dev.parent = dev; + hsotg->gadget.dev.dma_mask = dev->dma_mask; + + /* setup endpoint information */ + + INIT_LIST_HEAD(&hsotg->gadget.ep_list); + hsotg->gadget.ep0 = &hsotg->eps[0].ep; + + /* allocate EP0 request */ + + hsotg->ctrl_req = s3c_hsotg_ep_alloc_request(&hsotg->eps[0].ep, + GFP_KERNEL); + if (!hsotg->ctrl_req) { + dev_err(dev, "failed to allocate ctrl req\n"); + goto err_regs; + } + + /* reset the system */ + + s3c_hsotg_gate(pdev, true); + + s3c_hsotg_otgreset(hsotg); + s3c_hsotg_corereset(hsotg); + s3c_hsotg_init(hsotg); + + /* initialise the endpoints now the core has been initialised */ + for (epnum = 0; epnum < S3C_HSOTG_EPS; epnum++) + s3c_hsotg_initep(hsotg, &hsotg->eps[epnum], epnum); + + s3c_hsotg_create_debug(hsotg); + + s3c_hsotg_dump(hsotg); + + our_hsotg = hsotg; + return 0; + +err_regs: + iounmap(hsotg->regs); + +err_regs_res: + release_resource(hsotg->regs_res); + kfree(hsotg->regs_res); + +err_mem: + kfree(hsotg); + return ret; +} + +static int __devexit s3c_hsotg_remove(struct platform_device *pdev) +{ + struct s3c_hsotg *hsotg = platform_get_drvdata(pdev); + + s3c_hsotg_delete_debug(hsotg); + + usb_gadget_unregister_driver(hsotg->driver); + + free_irq(hsotg->irq, hsotg); + iounmap(hsotg->regs); + + release_resource(hsotg->regs_res); + kfree(hsotg->regs_res); + + s3c_hsotg_gate(pdev, false); + + kfree(hsotg); + return 0; +} + +#if 1 +#define s3c_hsotg_suspend NULL +#define s3c_hsotg_resume NULL +#endif + +static struct platform_driver s3c_hsotg_driver = { + .driver = { + .name = "s3c-hsotg", + .owner = THIS_MODULE, + }, + .probe = s3c_hsotg_probe, + .remove = __devexit_p(s3c_hsotg_remove), + .suspend = s3c_hsotg_suspend, + .resume = s3c_hsotg_resume, +}; + +static int __init s3c_hsotg_modinit(void) +{ + return platform_driver_register(&s3c_hsotg_driver); +} + +static void __exit s3c_hsotg_modexit(void) +{ + platform_driver_unregister(&s3c_hsotg_driver); +} + +module_init(s3c_hsotg_modinit); +module_exit(s3c_hsotg_modexit); + +MODULE_DESCRIPTION("Samsung S3C USB High-speed/OtG device"); +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c-hsotg"); -- cgit v1.2.3 From c6994e6f067cf0fc4c6cca3d164018b1150916f8 Mon Sep 17 00:00:00 2001 From: Bryan Wu <cooloney@kernel.org> Date: Wed, 3 Jun 2009 09:17:58 -0400 Subject: USB: gadget: add USB Audio Gadget driver Funtions added: - setup all the USB audio class device descriptors - handle class specific setup request - receive data from USB host by ISO transfer - play audio data by ALSA sound card - open and setup playback PCM interface - set default playback PCM parameters - provide playback functions for USB audio driver - provide PCM parameters set/get functions Test on: - Host: Ubuntu 8.10, kernel 2.6.27 - Gadget: EZKIT-BF548 with ASoC AD1980 codec Todo: - add real Mute control code - add real Volume control code - maybe find another way to replace dynamic buffer handling with static buffer allocation - test on Windows system - provide control interface to handle mute/volume control - provide capture interface in the future - test on BF527, other USB device controler and other audio codec Signed-off-by: Bryan Wu <cooloney@kernel.org> Signed-off-by: Mike Frysinger <vapier@gentoo.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/Kconfig | 14 + drivers/usb/gadget/Makefile | 2 + drivers/usb/gadget/audio.c | 302 ++++++++++++++++++ drivers/usb/gadget/f_audio.c | 707 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/u_audio.c | 319 +++++++++++++++++++ drivers/usb/gadget/u_audio.h | 56 ++++ 6 files changed, 1400 insertions(+) create mode 100644 drivers/usb/gadget/audio.c create mode 100644 drivers/usb/gadget/f_audio.c create mode 100644 drivers/usb/gadget/u_audio.c create mode 100644 drivers/usb/gadget/u_audio.h (limited to 'drivers') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 924bb7a1cecf..ef9d9086cddc 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -580,6 +580,20 @@ config USB_ZERO_HNPTEST the "B-Peripheral" role, that device will use HNP to let this one serve as the USB host instead (in the "B-Host" role). +config USB_AUDIO + tristate "Audio Gadget (EXPERIMENTAL)" + depends on SND + help + Gadget Audio is compatible with USB Audio Class specification 1.0. + It will include at least one AudioControl interface, zero or more + AudioStream interface and zero or more MIDIStream interface. + + Gadget Audio will use on-board ALSA (CONFIG_SND) audio card to + playback or capture audio stream. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_audio". + config USB_ETH tristate "Ethernet Gadget (with CDC Ethernet support)" depends on NET diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 9be2fbd6f5c2..fc9a7dc01047 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o # USB gadget drivers # g_zero-objs := zero.o +g_audio-objs := audio.o g_ether-objs := ether.o g_serial-objs := serial.o g_midi-objs := gmidi.o @@ -40,6 +41,7 @@ g_printer-objs := printer.o g_cdc-objs := cdc2.o obj-$(CONFIG_USB_ZERO) += g_zero.o +obj-$(CONFIG_USB_AUDIO) += g_audio.o obj-$(CONFIG_USB_ETH) += g_ether.o obj-$(CONFIG_USB_GADGETFS) += gadgetfs.o obj-$(CONFIG_USB_FILE_STORAGE) += g_file_storage.o diff --git a/drivers/usb/gadget/audio.c b/drivers/usb/gadget/audio.c new file mode 100644 index 000000000000..94de7e864614 --- /dev/null +++ b/drivers/usb/gadget/audio.c @@ -0,0 +1,302 @@ +/* + * audio.c -- Audio gadget driver + * + * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +/* #define VERBOSE_DEBUG */ + +#include <linux/kernel.h> +#include <linux/utsname.h> + +#include "u_audio.h" + +#define DRIVER_DESC "Linux USB Audio Gadget" +#define DRIVER_VERSION "Dec 18, 2008" + +/*-------------------------------------------------------------------------*/ + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "composite.c" +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + +#include "u_audio.c" +#include "f_audio.c" + +/*-------------------------------------------------------------------------*/ + +/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* Thanks to NetChip Technologies for donating this product ID. */ +#define AUDIO_VENDOR_NUM 0x0525 /* NetChip */ +#define AUDIO_PRODUCT_NUM 0xa4a1 /* Linux-USB Audio Gadget */ + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = __constant_cpu_to_le16(0x200), + + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id defaults change according to what configs + * we support. (As does bNumConfigurations.) These values can + * also be overridden by module parameters. + */ + .idVendor = __constant_cpu_to_le16(AUDIO_VENDOR_NUM), + .idProduct = __constant_cpu_to_le16(AUDIO_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +/** + * Handle USB audio endpoint set/get command in setup class request + */ + +static int audio_set_endpoint_req(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = c->cdev; + int value = -EOPNOTSUPP; + u16 ep = le16_to_cpu(ctrl->wIndex); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case SET_CUR: + value = 0; + break; + + case SET_MIN: + break; + + case SET_MAX: + break; + + case SET_RES: + break; + + case SET_MEM: + break; + + default: + break; + } + + return value; +} + +static int audio_get_endpoint_req(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = c->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case GET_CUR: + case GET_MIN: + case GET_MAX: + case GET_RES: + value = 3; + break; + case GET_MEM: + break; + default: + break; + } + + return value; +} + +static int +audio_setup(struct usb_configuration *c, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = c->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything except + * Audio class messages; interface activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_AUDIO_SET_ENDPOINT: + value = audio_set_endpoint_req(c, ctrl); + break; + + case USB_AUDIO_GET_ENDPOINT: + value = audio_get_endpoint_req(c, ctrl); + break; + + default: + ERROR(cdev, "Invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "Audio req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "Audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +/*-------------------------------------------------------------------------*/ + +static int __init audio_do_config(struct usb_configuration *c) +{ + /* FIXME alloc iConfiguration string, set it in c->strings */ + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + audio_bind_config(c); + + return 0; +} + +static struct usb_configuration audio_config_driver = { + .label = DRIVER_DESC, + .bind = audio_do_config, + .setup = audio_setup, + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init audio_bind(struct usb_composite_dev *cdev) +{ + int gcnum; + int status; + + gcnum = usb_gadget_controller_number(cdev->gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0300 | gcnum); + else { + ERROR(cdev, "controller '%s' not recognized; trying %s\n", + cdev->gadget->name, + audio_config_driver.label); + device_desc.bcdDevice = + __constant_cpu_to_le16(0x0300 | 0x0099); + } + + /* device descriptor strings: manufacturer, product */ + snprintf(manufacturer, sizeof manufacturer, "%s %s with %s", + init_utsname()->sysname, init_utsname()->release, + cdev->gadget->name); + status = usb_string_id(cdev); + if (status < 0) + goto fail; + strings_dev[STRING_MANUFACTURER_IDX].id = status; + device_desc.iManufacturer = status; + + status = usb_string_id(cdev); + if (status < 0) + goto fail; + strings_dev[STRING_PRODUCT_IDX].id = status; + device_desc.iProduct = status; + + status = usb_add_config(cdev, &audio_config_driver); + if (status < 0) + goto fail; + + INFO(cdev, "%s, version: %s\n", DRIVER_DESC, DRIVER_VERSION); + return 0; + +fail: + return status; +} + +static int __exit audio_unbind(struct usb_composite_dev *cdev) +{ + return 0; +} + +static struct usb_composite_driver audio_driver = { + .name = "g_audio", + .dev = &device_desc, + .strings = audio_strings, + .bind = audio_bind, + .unbind = __exit_p(audio_unbind), +}; + +static int __init init(void) +{ + return usb_composite_register(&audio_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&audio_driver); +} +module_exit(cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Bryan Wu <cooloney@kernel.org>"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/gadget/f_audio.c b/drivers/usb/gadget/f_audio.c new file mode 100644 index 000000000000..66527ba2d2ea --- /dev/null +++ b/drivers/usb/gadget/f_audio.c @@ -0,0 +1,707 @@ +/* + * f_audio.c -- USB Audio class function driver + * + * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <asm/atomic.h> + +#include "u_audio.h" + +#define OUT_EP_MAX_PACKET_SIZE 200 +static int req_buf_size = OUT_EP_MAX_PACKET_SIZE; +module_param(req_buf_size, int, S_IRUGO); +MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size"); + +static int req_count = 256; +module_param(req_count, int, S_IRUGO); +MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count"); + +static int audio_buf_size = 48000; +module_param(audio_buf_size, int, S_IRUGO); +MODULE_PARM_DESC(audio_buf_size, "Audio buffer size"); + +/* + * DESCRIPTORS ... most are static, but strings and full + * configuration descriptors are built on demand. + */ + +/* + * We have two interfaces- AudioControl and AudioStreaming + * TODO: only supcard playback currently + */ +#define F_AUDIO_AC_INTERFACE 0 +#define F_AUDIO_AS_INTERFACE 1 +#define F_AUDIO_NUM_INTERFACES 2 + +/* B.3.1 Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc __initdata = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +DECLARE_USB_AC_HEADER_DESCRIPTOR(2); + +#define USB_DT_AC_HEADER_LENGH USB_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct usb_ac_header_descriptor_2 ac_header_desc = { + .bLength = USB_DT_AC_HEADER_LENGH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = HEADER, + .bcdADC = __constant_cpu_to_le16(0x0100), + .wTotalLength = __constant_cpu_to_le16(USB_DT_AC_HEADER_LENGH), + .bInCollection = F_AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + [0] = F_AUDIO_AC_INTERFACE, + [1] = F_AUDIO_AS_INTERFACE, + } +}; + +#define INPUT_TERMINAL_ID 1 +static struct usb_input_terminal_descriptor input_terminal_desc = { + .bLength = USB_DT_AC_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = INPUT_TERMINAL, + .bTerminalID = INPUT_TERMINAL_ID, + .wTerminalType = USB_AC_TERMINAL_STREAMING, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +DECLARE_USB_AC_FEATURE_UNIT_DESCRIPTOR(0); + +#define FEATURE_UNIT_ID 2 +static struct usb_ac_feature_unit_descriptor_0 feature_unit_desc = { + .bLength = USB_DT_AC_FEATURE_UNIT_SIZE(0), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = FEATURE_UNIT, + .bUnitID = FEATURE_UNIT_ID, + .bSourceID = INPUT_TERMINAL_ID, + .bControlSize = 2, + .bmaControls[0] = (FU_MUTE | FU_VOLUME), +}; + +static struct usb_audio_control mute_control = { + .list = LIST_HEAD_INIT(mute_control.list), + .name = "Mute Control", + .type = MUTE_CONTROL, + /* Todo: add real Mute control code */ + .set = generic_set_cmd, + .get = generic_get_cmd, +}; + +static struct usb_audio_control volume_control = { + .list = LIST_HEAD_INIT(volume_control.list), + .name = "Volume Control", + .type = VOLUME_CONTROL, + /* Todo: add real Volume control code */ + .set = generic_set_cmd, + .get = generic_get_cmd, +}; + +static struct usb_audio_control_selector feature_unit = { + .list = LIST_HEAD_INIT(feature_unit.list), + .id = FEATURE_UNIT_ID, + .name = "Mute & Volume Control", + .type = FEATURE_UNIT, + .desc = (struct usb_descriptor_header *)&feature_unit_desc, +}; + +#define OUTPUT_TERMINAL_ID 3 +static struct usb_output_terminal_descriptor output_terminal_desc = { + .bLength = USB_DT_AC_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = OUTPUT_TERMINAL, + .bTerminalID = OUTPUT_TERMINAL_ID, + .wTerminalType = USB_AC_OUTPUT_TERMINAL_SPEAKER, + .bAssocTerminal = FEATURE_UNIT_ID, + .bSourceID = FEATURE_UNIT_ID, +}; + +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +/* B.4.2 Class-Specific AS Interface Descriptor */ +static struct usb_as_header_descriptor as_header_desc = { + .bLength = USB_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = AS_GENERAL, + .bTerminalLink = INPUT_TERMINAL_ID, + .bDelay = 1, + .wFormatTag = USB_AS_AUDIO_FORMAT_TYPE_I_PCM, +}; + +DECLARE_USB_AS_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct usb_as_formate_type_i_discrete_descriptor_1 as_type_i_desc = { + .bLength = USB_AS_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = FORMAT_TYPE, + .bFormatType = USB_AS_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor as_out_ep_desc __initdata = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_AS_ENDPOINT_ADAPTIVE + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct usb_as_iso_endpoint_descriptor as_iso_out_desc __initdata = { + .bLength = USB_AS_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = __constant_cpu_to_le16(1), +}; + +static struct usb_descriptor_header *f_audio_desc[] __initdata = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&as_out_ep_desc, + (struct usb_descriptor_header *)&as_iso_out_desc, + NULL, +}; + +/* string IDs are assigned dynamically */ + +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 + +static char manufacturer[50]; + +static struct usb_string strings_dev[] = { + [STRING_MANUFACTURER_IDX].s = manufacturer, + [STRING_PRODUCT_IDX].s = DRIVER_DESC, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *audio_strings[] = { + &stringtab_dev, + NULL, +}; + +/* + * This function is an ALSA sound card following USB Audio Class Spec 1.0. + */ + +/*-------------------------------------------------------------------------*/ +struct f_audio_buf { + u8 *buf; + int actual; + struct list_head list; +}; + +static struct f_audio_buf *f_audio_buffer_alloc(int buf_size) +{ + struct f_audio_buf *copy_buf; + + copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC); + if (!copy_buf) + return (struct f_audio_buf *)-ENOMEM; + + copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC); + if (!copy_buf->buf) { + kfree(copy_buf); + return (struct f_audio_buf *)-ENOMEM; + } + + return copy_buf; +} + +static void f_audio_buffer_free(struct f_audio_buf *audio_buf) +{ + kfree(audio_buf->buf); + kfree(audio_buf); +} +/*-------------------------------------------------------------------------*/ + +struct f_audio { + struct gaudio card; + + /* endpoints handle full and/or high speeds */ + struct usb_ep *out_ep; + struct usb_endpoint_descriptor *out_desc; + + spinlock_t lock; + struct f_audio_buf *copy_buf; + struct work_struct playback_work; + struct list_head play_queue; + + /* Control Set command */ + struct list_head cs; + u8 set_cmd; + struct usb_audio_control *set_con; +}; + +static inline struct f_audio *func_to_audio(struct usb_function *f) +{ + return container_of(f, struct f_audio, card.func); +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_playback_work(struct work_struct *data) +{ + struct f_audio *audio = container_of(data, struct f_audio, + playback_work); + struct f_audio_buf *play_buf; + + spin_lock_irq(&audio->lock); + if (list_empty(&audio->play_queue)) { + spin_unlock_irq(&audio->lock); + return; + } + play_buf = list_first_entry(&audio->play_queue, + struct f_audio_buf, list); + list_del(&play_buf->list); + spin_unlock_irq(&audio->lock); + + u_audio_playback(&audio->card, play_buf->buf, play_buf->actual); + f_audio_buffer_free(play_buf); + + return; +} + +static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_audio *audio = req->context; + struct usb_composite_dev *cdev = audio->card.func.config->cdev; + struct f_audio_buf *copy_buf = audio->copy_buf; + int err; + + if (!copy_buf) + return -EINVAL; + + /* Copy buffer is full, add it to the play_queue */ + if (audio_buf_size - copy_buf->actual < req->actual) { + list_add_tail(©_buf->list, &audio->play_queue); + schedule_work(&audio->playback_work); + copy_buf = f_audio_buffer_alloc(audio_buf_size); + if (copy_buf < 0) + return -ENOMEM; + } + + memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual); + copy_buf->actual += req->actual; + audio->copy_buf = copy_buf; + + err = usb_ep_queue(ep, req, GFP_ATOMIC); + if (err) + ERROR(cdev, "%s queue req: %d\n", ep->name, err); + + return 0; + +} + +static void f_audio_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_audio *audio = req->context; + int status = req->status; + u32 data = 0; + struct usb_ep *out_ep = audio->out_ep; + + switch (status) { + + case 0: /* normal completion? */ + if (ep == out_ep) + f_audio_out_ep_complete(ep, req); + else if (audio->set_con) { + memcpy(&data, req->buf, req->length); + audio->set_con->set(audio->set_con, audio->set_cmd, + le16_to_cpu(data)); + audio->set_con = NULL; + } + break; + default: + break; + } +} + +static int audio_set_intf_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 con_sel = (w_value >> 8) & 0xFF; + u8 cmd = (ctrl->bRequest & 0x0F); + struct usb_audio_control_selector *cs; + struct usb_audio_control *con; + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", + ctrl->bRequest, w_value, len, id); + + list_for_each_entry(cs, &audio->cs, list) { + if (cs->id == id) { + list_for_each_entry(con, &cs->control, list) { + if (con->type == con_sel) { + audio->set_con = con; + break; + } + } + break; + } + } + + audio->set_cmd = cmd; + req->context = audio; + req->complete = f_audio_complete; + + return len; +} + +static int audio_get_intf_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 con_sel = (w_value >> 8) & 0xFF; + u8 cmd = (ctrl->bRequest & 0x0F); + struct usb_audio_control_selector *cs; + struct usb_audio_control *con; + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", + ctrl->bRequest, w_value, len, id); + + list_for_each_entry(cs, &audio->cs, list) { + if (cs->id == id) { + list_for_each_entry(con, &cs->control, list) { + if (con->type == con_sel && con->get) { + value = con->get(con, cmd); + break; + } + } + break; + } + } + + req->context = audio; + req->complete = f_audio_complete; + memcpy(req->buf, &value, len); + + return len; +} + +static int +f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything except + * Audio class messages; interface activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_AUDIO_SET_INTF: + value = audio_set_intf_req(f, ctrl); + break; + + case USB_AUDIO_GET_INTF: + value = audio_get_intf_req(f, ctrl); + break; + + default: + ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_ep *out_ep = audio->out_ep; + struct usb_request *req; + int i = 0, err = 0; + + DBG(cdev, "intf %d, alt %d\n", intf, alt); + + if (intf == 1) { + if (alt == 1) { + usb_ep_enable(out_ep, audio->out_desc); + out_ep->driver_data = audio; + audio->copy_buf = f_audio_buffer_alloc(audio_buf_size); + + /* + * allocate a bunch of read buffers + * and queue them all at once. + */ + for (i = 0; i < req_count && err == 0; i++) { + req = usb_ep_alloc_request(out_ep, GFP_ATOMIC); + if (req) { + req->buf = kzalloc(req_buf_size, + GFP_ATOMIC); + if (req->buf) { + req->length = req_buf_size; + req->context = audio; + req->complete = + f_audio_complete; + err = usb_ep_queue(out_ep, + req, GFP_ATOMIC); + if (err) + ERROR(cdev, + "%s queue req: %d\n", + out_ep->name, err); + } else + err = -ENOMEM; + } else + err = -ENOMEM; + } + + } else { + struct f_audio_buf *copy_buf = audio->copy_buf; + if (copy_buf) { + list_add_tail(©_buf->list, + &audio->play_queue); + schedule_work(&audio->playback_work); + } + } + } + + return err; +} + +static void f_audio_disable(struct usb_function *f) +{ + return; +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_build_desc(struct f_audio *audio) +{ + struct gaudio *card = &audio->card; + u8 *sam_freq; + int rate; + + /* Set channel numbers */ + input_terminal_desc.bNrChannels = u_audio_get_playback_channels(card); + as_type_i_desc.bNrChannels = u_audio_get_playback_channels(card); + + /* Set sample rates */ + rate = u_audio_get_playback_rate(card); + sam_freq = as_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); + + /* Todo: Set Sample bits and other parameters */ + + return; +} + +/* audio function driver setup/binding */ +static int __init +f_audio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_audio *audio = func_to_audio(f); + int status; + struct usb_ep *ep; + + f_audio_build_desc(audio); + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ac_interface_desc.bInterfaceNumber = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_interface_alt_0_desc.bInterfaceNumber = status; + as_interface_alt_1_desc.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); + if (!ep) + goto fail; + audio->out_ep = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* supcard all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + + /* copy descriptors, and track endpoint copies */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + c->highspeed = true; + f->hs_descriptors = usb_copy_descriptors(f_audio_desc); + } else + f->descriptors = usb_copy_descriptors(f_audio_desc); + + return 0; + +fail: + + return status; +} + +static void +f_audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_audio *audio = func_to_audio(f); + + usb_free_descriptors(f->descriptors); + kfree(audio); +} + +/*-------------------------------------------------------------------------*/ + +/* Todo: add more control selecotor dynamically */ +int __init control_selector_init(struct f_audio *audio) +{ + INIT_LIST_HEAD(&audio->cs); + list_add(&feature_unit.list, &audio->cs); + + INIT_LIST_HEAD(&feature_unit.control); + list_add(&mute_control.list, &feature_unit.control); + list_add(&volume_control.list, &feature_unit.control); + + volume_control.data[_CUR] = 0xffc0; + volume_control.data[_MIN] = 0xe3a0; + volume_control.data[_MAX] = 0xfff0; + volume_control.data[_RES] = 0x0030; + + return 0; +} + +/** + * audio_bind_config - add USB audio fucntion to a configuration + * @c: the configuration to supcard the USB audio function + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + */ +int __init audio_bind_config(struct usb_configuration *c) +{ + struct f_audio *audio; + int status; + + /* allocate and initialize one new instance */ + audio = kzalloc(sizeof *audio, GFP_KERNEL); + if (!audio) + return -ENOMEM; + + audio->card.func.name = "g_audio"; + audio->card.gadget = c->cdev->gadget; + + INIT_LIST_HEAD(&audio->play_queue); + spin_lock_init(&audio->lock); + + /* set up ASLA audio devices */ + status = gaudio_setup(&audio->card); + if (status < 0) + goto setup_fail; + + audio->card.func.strings = audio_strings; + audio->card.func.bind = f_audio_bind; + audio->card.func.unbind = f_audio_unbind; + audio->card.func.set_alt = f_audio_set_alt; + audio->card.func.setup = f_audio_setup; + audio->card.func.disable = f_audio_disable; + audio->out_desc = &as_out_ep_desc; + + control_selector_init(audio); + + INIT_WORK(&audio->playback_work, f_audio_playback_work); + + status = usb_add_function(c, &audio->card.func); + if (status) + goto add_fail; + + INFO(c->cdev, "audio_buf_size %d, req_buf_size %d, req_count %d\n", + audio_buf_size, req_buf_size, req_count); + + return status; + +add_fail: + gaudio_cleanup(&audio->card); +setup_fail: + kfree(audio); + return status; +} diff --git a/drivers/usb/gadget/u_audio.c b/drivers/usb/gadget/u_audio.c new file mode 100644 index 000000000000..0f3d22fc030e --- /dev/null +++ b/drivers/usb/gadget/u_audio.c @@ -0,0 +1,319 @@ +/* + * u_audio.c -- ALSA audio utilities for Gadget stack + * + * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/kernel.h> +#include <linux/utsname.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/random.h> +#include <linux/syscalls.h> + +#include "u_audio.h" + +/* + * This component encapsulates the ALSA devices for USB audio gadget + */ + +#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p" +#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c" +#define FILE_CONTROL "/dev/snd/controlC0" + +static char *fn_play = FILE_PCM_PLAYBACK; +module_param(fn_play, charp, S_IRUGO); +MODULE_PARM_DESC(fn_play, "Playback PCM device file name"); + +static char *fn_cap = FILE_PCM_CAPTURE; +module_param(fn_cap, charp, S_IRUGO); +MODULE_PARM_DESC(fn_cap, "Capture PCM device file name"); + +static char *fn_cntl = FILE_CONTROL; +module_param(fn_cntl, charp, S_IRUGO); +MODULE_PARM_DESC(fn_cntl, "Control device file name"); + +/*-------------------------------------------------------------------------*/ + +/** + * Some ALSA internal helper functions + */ +static int snd_interval_refine_set(struct snd_interval *i, unsigned int val) +{ + struct snd_interval t; + t.empty = 0; + t.min = t.max = val; + t.openmin = t.openmax = 0; + t.integer = 1; + return snd_interval_refine(i, &t); +} + +static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed; + if (hw_is_mask(var)) { + struct snd_mask *m = hw_param_mask(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_mask_none(m); + } else { + if (dir > 0) + val++; + else if (dir < 0) + val--; + changed = snd_mask_refine_set( + hw_param_mask(params, var), val); + } + } else if (hw_is_interval(var)) { + struct snd_interval *i = hw_param_interval(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_interval_none(i); + } else if (dir == 0) + changed = snd_interval_refine_set(i, val); + else { + struct snd_interval t; + t.openmin = 1; + t.openmax = 1; + t.empty = 0; + t.integer = 0; + if (dir < 0) { + t.min = val - 1; + t.max = val; + } else { + t.min = val; + t.max = val+1; + } + changed = snd_interval_refine(i, &t); + } + } else + return -EINVAL; + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} +/*-------------------------------------------------------------------------*/ + +/** + * Set default hardware params + */ +static int playback_default_hw_params(struct gaudio_snd_dev *snd) +{ + struct snd_pcm_substream *substream = snd->substream; + struct snd_pcm_hw_params *params; + snd_pcm_sframes_t result; + + /* + * SNDRV_PCM_ACCESS_RW_INTERLEAVED, + * SNDRV_PCM_FORMAT_S16_LE + * CHANNELS: 2 + * RATE: 48000 + */ + snd->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + snd->format = SNDRV_PCM_FORMAT_S16_LE; + snd->channels = 2; + snd->rate = 48000; + + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, + snd->access, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, + snd->format, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + snd->channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, + snd->rate, 0); + + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params); + + result = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL); + if (result < 0) { + ERROR(snd->card, + "Preparing sound card failed: %d\n", (int)result); + kfree(params); + return result; + } + + /* Store the hardware parameters */ + snd->access = params_access(params); + snd->format = params_format(params); + snd->channels = params_channels(params); + snd->rate = params_rate(params); + + kfree(params); + + INFO(snd->card, + "Hardware params: access %x, format %x, channels %d, rate %d\n", + snd->access, snd->format, snd->channels, snd->rate); + + return 0; +} + +/** + * Playback audio buffer data by ALSA PCM device + */ +static size_t u_audio_playback(struct gaudio *card, void *buf, size_t count) +{ + struct gaudio_snd_dev *snd = &card->playback; + struct snd_pcm_substream *substream = snd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + mm_segment_t old_fs; + ssize_t result; + snd_pcm_sframes_t frames; + +try_again: + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { + result = snd_pcm_kernel_ioctl(substream, + SNDRV_PCM_IOCTL_PREPARE, NULL); + if (result < 0) { + ERROR(card, "Preparing sound card failed: %d\n", + (int)result); + return result; + } + } + + frames = bytes_to_frames(runtime, count); + old_fs = get_fs(); + set_fs(KERNEL_DS); + result = snd_pcm_lib_write(snd->substream, buf, frames); + if (result != frames) { + ERROR(card, "Playback error: %d\n", (int)result); + set_fs(old_fs); + goto try_again; + } + set_fs(old_fs); + + return 0; +} + +static int u_audio_get_playback_channels(struct gaudio *card) +{ + return card->playback.channels; +} + +static int u_audio_get_playback_rate(struct gaudio *card) +{ + return card->playback.rate; +} + +/** + * Open ALSA PCM and control device files + * Initial the PCM or control device + */ +static int gaudio_open_snd_dev(struct gaudio *card) +{ + struct snd_pcm_file *pcm_file; + struct gaudio_snd_dev *snd; + + if (!card) + return -ENODEV; + + /* Open control device */ + snd = &card->control; + snd->filp = filp_open(fn_cntl, O_RDWR, 0); + if (IS_ERR(snd->filp)) { + int ret = PTR_ERR(snd->filp); + ERROR(card, "unable to open sound control device file: %s\n", + fn_cntl); + snd->filp = NULL; + return ret; + } + snd->card = card; + + /* Open PCM playback device and setup substream */ + snd = &card->playback; + snd->filp = filp_open(fn_play, O_WRONLY, 0); + if (IS_ERR(snd->filp)) { + ERROR(card, "No such PCM playback device: %s\n", fn_play); + snd->filp = NULL; + } + pcm_file = snd->filp->private_data; + snd->substream = pcm_file->substream; + snd->card = card; + playback_default_hw_params(snd); + + /* Open PCM capture device and setup substream */ + snd = &card->capture; + snd->filp = filp_open(fn_cap, O_RDONLY, 0); + if (IS_ERR(snd->filp)) { + ERROR(card, "No such PCM capture device: %s\n", fn_cap); + snd->filp = NULL; + } + pcm_file = snd->filp->private_data; + snd->substream = pcm_file->substream; + snd->card = card; + + return 0; +} + +/** + * Close ALSA PCM and control device files + */ +static int gaudio_close_snd_dev(struct gaudio *gau) +{ + struct gaudio_snd_dev *snd; + + /* Close control device */ + snd = &gau->control; + if (!IS_ERR(snd->filp)) + filp_close(snd->filp, current->files); + + /* Close PCM playback device and setup substream */ + snd = &gau->playback; + if (!IS_ERR(snd->filp)) + filp_close(snd->filp, current->files); + + /* Close PCM capture device and setup substream */ + snd = &gau->capture; + if (!IS_ERR(snd->filp)) + filp_close(snd->filp, current->files); + + return 0; +} + +/** + * gaudio_setup - setup ALSA interface and preparing for USB transfer + * + * This sets up PCM, mixer or MIDI ALSA devices fore USB gadget using. + * + * Returns negative errno, or zero on success + */ +int __init gaudio_setup(struct gaudio *card) +{ + int ret; + + ret = gaudio_open_snd_dev(card); + if (ret) + ERROR(card, "we need at least one control device\n"); + + return ret; + +} + +/** + * gaudio_cleanup - remove ALSA device interface + * + * This is called to free all resources allocated by @gaudio_setup(). + */ +void gaudio_cleanup(struct gaudio *card) +{ + if (card) + gaudio_close_snd_dev(card); +} + diff --git a/drivers/usb/gadget/u_audio.h b/drivers/usb/gadget/u_audio.h new file mode 100644 index 000000000000..cc8d159c648a --- /dev/null +++ b/drivers/usb/gadget/u_audio.h @@ -0,0 +1,56 @@ +/* + * u_audio.h -- interface to USB gadget "ALSA AUDIO" utilities + * + * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __U_AUDIO_H +#define __U_AUDIO_H + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/usb/audio.h> +#include <linux/usb/composite.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "gadget_chips.h" + +/* + * This represents the USB side of an audio card device, managed by a USB + * function which provides control and stream interfaces. + */ + +struct gaudio_snd_dev { + struct gaudio *card; + struct file *filp; + struct snd_pcm_substream *substream; + int access; + int format; + int channels; + int rate; +}; + +struct gaudio { + struct usb_function func; + struct usb_gadget *gadget; + + /* ALSA sound device interfaces */ + struct gaudio_snd_dev control; + struct gaudio_snd_dev playback; + struct gaudio_snd_dev capture; + + /* TODO */ +}; + +int gaudio_setup(struct gaudio *card); +void gaudio_cleanup(struct gaudio *card); + +#endif /* __U_AUDIO_H */ -- cgit v1.2.3 From c706ebdfc8955b850e477255a8c0f93f9f14712d Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Tue, 2 Jun 2009 11:54:11 -0400 Subject: USB: usb-serial: call port_probe and port_remove at the right times This patch (as1253) prevents the usb-serial core from calling a driver's port_probe and port_remove methods more than once per port. It also removes some unnecessary try_module_get() calls and adds a missing port_remove method call in a failure path. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/serial/bus.c | 27 +++++++++++---------------- drivers/usb/serial/usb-serial.c | 24 ++++++++++++++++++++++-- include/linux/usb/serial.h | 8 ++++++++ 3 files changed, 41 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index 83bbb5bca2ef..ba555c528cc6 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -59,23 +59,22 @@ static int usb_serial_device_probe(struct device *dev) retval = -ENODEV; goto exit; } + if (port->dev_state != PORT_REGISTERING) + goto exit; driver = port->serial->type; if (driver->port_probe) { - if (!try_module_get(driver->driver.owner)) { - dev_err(dev, "module get failed, exiting\n"); - retval = -EIO; - goto exit; - } retval = driver->port_probe(port); - module_put(driver->driver.owner); if (retval) goto exit; } retval = device_create_file(dev, &dev_attr_port_number); - if (retval) + if (retval) { + if (driver->port_remove) + retval = driver->port_remove(port); goto exit; + } minor = port->number; tty_register_device(usb_serial_tty_driver, minor, dev); @@ -98,19 +97,15 @@ static int usb_serial_device_remove(struct device *dev) if (!port) return -ENODEV; + if (port->dev_state != PORT_UNREGISTERING) + return retval; + device_remove_file(&port->dev, &dev_attr_port_number); driver = port->serial->type; - if (driver->port_remove) { - if (!try_module_get(driver->driver.owner)) { - dev_err(dev, "module get failed, exiting\n"); - retval = -EIO; - goto exit; - } + if (driver->port_remove) retval = driver->port_remove(port); - module_put(driver->driver.owner); - } -exit: + minor = port->number; tty_unregister_device(usb_serial_tty_driver, minor); dev_info(dev, "%s converter now disconnected from ttyUSB%d\n", diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 1967a7edc10c..da890f030fac 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1046,10 +1046,15 @@ int usb_serial_probe(struct usb_interface *interface, dev_set_name(&port->dev, "ttyUSB%d", port->number); dbg ("%s - registering %s", __func__, dev_name(&port->dev)); + port->dev_state = PORT_REGISTERING; retval = device_register(&port->dev); - if (retval) + if (retval) { dev_err(&port->dev, "Error registering port device, " "continuing\n"); + port->dev_state = PORT_UNREGISTERED; + } else { + port->dev_state = PORT_REGISTERED; + } } usb_serial_console_init(debug, minor); @@ -1130,7 +1135,22 @@ void usb_serial_disconnect(struct usb_interface *interface) } kill_traffic(port); cancel_work_sync(&port->work); - device_del(&port->dev); + if (port->dev_state == PORT_REGISTERED) { + + /* Make sure the port is bound so that the + * driver's port_remove method is called. + */ + if (!port->dev.driver) { + int rc; + + port->dev.driver = + &serial->type->driver; + rc = device_bind_driver(&port->dev); + } + port->dev_state = PORT_UNREGISTERING; + device_del(&port->dev); + port->dev_state = PORT_UNREGISTERED; + } } } serial->type->shutdown(serial); diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index e29ebcf3287b..ed4aa0fa7ed8 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -27,6 +27,13 @@ /* parity check flag */ #define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) +enum port_dev_state { + PORT_UNREGISTERED, + PORT_REGISTERING, + PORT_REGISTERED, + PORT_UNREGISTERING, +}; + /** * usb_serial_port: structure for the specific ports of a device. * @serial: pointer back to the struct usb_serial owner of this port. @@ -102,6 +109,7 @@ struct usb_serial_port { char console; unsigned long sysrq; /* sysrq timeout */ struct device dev; + enum port_dev_state dev_state; }; #define to_usb_serial_port(d) container_of(d, struct usb_serial_port, dev) -- cgit v1.2.3 From f9c99bb8b3a1ec81af68d484a551307326c2e933 Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Tue, 2 Jun 2009 11:53:55 -0400 Subject: USB: usb-serial: replace shutdown with disconnect, release This patch (as1254) splits up the shutdown method of usb_serial_driver into a disconnect and a release method. The problem is that the usb-serial core was calling shutdown during disconnect handling, but drivers didn't expect it to be called until after all the open file references had been closed. The result was an oops when the close method tried to use memory that had been deallocated by shutdown. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/staging/uc2322/aten2011.c | 4 ++-- drivers/usb/serial/aircable.c | 5 ++--- drivers/usb/serial/belkin_sa.c | 7 +++--- drivers/usb/serial/cp210x.c | 6 ++--- drivers/usb/serial/cyberjack.c | 20 ++++++++++++----- drivers/usb/serial/cypress_m8.c | 11 +++++---- drivers/usb/serial/digi_acceleport.c | 20 ++++++++++++----- drivers/usb/serial/empeg.c | 8 ------- drivers/usb/serial/ftdi_sio.c | 14 ------------ drivers/usb/serial/garmin_gps.c | 16 ++++++++++--- drivers/usb/serial/generic.c | 9 ++++++-- drivers/usb/serial/io_edgeport.c | 29 ++++++++++++++++-------- drivers/usb/serial/io_tables.h | 12 ++++++---- drivers/usb/serial/io_ti.c | 22 +++++++++++++----- drivers/usb/serial/ipaq.c | 7 ------ drivers/usb/serial/iuu_phoenix.c | 6 ++--- drivers/usb/serial/keyspan.c | 13 ++++++++++- drivers/usb/serial/keyspan.h | 12 ++++++---- drivers/usb/serial/keyspan_pda.c | 4 ++-- drivers/usb/serial/kl5kusb105.c | 39 ++++++++++++++++++-------------- drivers/usb/serial/kobil_sct.c | 12 ++++------ drivers/usb/serial/mct_u232.c | 13 +++++------ drivers/usb/serial/mos7720.c | 9 +++----- drivers/usb/serial/mos7840.c | 42 ++++++++++++++++++++++++++++++----- drivers/usb/serial/omninet.c | 19 ++++++++++++---- drivers/usb/serial/opticon.c | 14 +++++++++--- drivers/usb/serial/option.c | 17 +++++++++----- drivers/usb/serial/oti6858.c | 7 +++--- drivers/usb/serial/pl2303.c | 5 ++--- drivers/usb/serial/sierra.c | 4 ++-- drivers/usb/serial/spcp8x5.c | 5 ++--- drivers/usb/serial/symbolserial.c | 14 +++++++++--- drivers/usb/serial/ti_usb_3410_5052.c | 10 ++++----- drivers/usb/serial/usb-serial.c | 29 +++++++++++------------- drivers/usb/serial/visor.c | 13 +++++------ drivers/usb/serial/whiteheat.c | 6 ++--- include/linux/usb/serial.h | 12 ++++++---- 37 files changed, 297 insertions(+), 198 deletions(-) (limited to 'drivers') diff --git a/drivers/staging/uc2322/aten2011.c b/drivers/staging/uc2322/aten2011.c index 9c62f787cc9c..39d0926d1a90 100644 --- a/drivers/staging/uc2322/aten2011.c +++ b/drivers/staging/uc2322/aten2011.c @@ -2336,7 +2336,7 @@ static int ATEN2011_startup(struct usb_serial *serial) return 0; } -static void ATEN2011_shutdown(struct usb_serial *serial) +static void ATEN2011_release(struct usb_serial *serial) { int i; struct ATENINTL_port *ATEN2011_port; @@ -2382,7 +2382,7 @@ static struct usb_serial_driver aten_serial_driver = { .tiocmget = ATEN2011_tiocmget, .tiocmset = ATEN2011_tiocmset, .attach = ATEN2011_startup, - .shutdown = ATEN2011_shutdown, + .release = ATEN2011_release, .read_bulk_callback = ATEN2011_bulk_in_callback, .read_int_callback = ATEN2011_interrupt_callback, }; diff --git a/drivers/usb/serial/aircable.c b/drivers/usb/serial/aircable.c index 6d106e74265e..2cbfab3716e5 100644 --- a/drivers/usb/serial/aircable.c +++ b/drivers/usb/serial/aircable.c @@ -364,7 +364,7 @@ static int aircable_attach(struct usb_serial *serial) return 0; } -static void aircable_shutdown(struct usb_serial *serial) +static void aircable_release(struct usb_serial *serial) { struct usb_serial_port *port = serial->port[0]; @@ -375,7 +375,6 @@ static void aircable_shutdown(struct usb_serial *serial) if (priv) { serial_buf_free(priv->tx_buf); serial_buf_free(priv->rx_buf); - usb_set_serial_port_data(port, NULL); kfree(priv); } } @@ -601,7 +600,7 @@ static struct usb_serial_driver aircable_device = { .num_ports = 1, .attach = aircable_attach, .probe = aircable_probe, - .shutdown = aircable_shutdown, + .release = aircable_release, .write = aircable_write, .write_room = aircable_write_room, .write_bulk_callback = aircable_write_bulk_callback, diff --git a/drivers/usb/serial/belkin_sa.c b/drivers/usb/serial/belkin_sa.c index 2bfd6dd85b5a..7033b031b443 100644 --- a/drivers/usb/serial/belkin_sa.c +++ b/drivers/usb/serial/belkin_sa.c @@ -90,7 +90,7 @@ static int debug; /* function prototypes for a Belkin USB Serial Adapter F5U103 */ static int belkin_sa_startup(struct usb_serial *serial); -static void belkin_sa_shutdown(struct usb_serial *serial); +static void belkin_sa_release(struct usb_serial *serial); static int belkin_sa_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp); static void belkin_sa_close(struct usb_serial_port *port); @@ -142,7 +142,7 @@ static struct usb_serial_driver belkin_device = { .tiocmget = belkin_sa_tiocmget, .tiocmset = belkin_sa_tiocmset, .attach = belkin_sa_startup, - .shutdown = belkin_sa_shutdown, + .release = belkin_sa_release, }; @@ -197,14 +197,13 @@ static int belkin_sa_startup(struct usb_serial *serial) } -static void belkin_sa_shutdown(struct usb_serial *serial) +static void belkin_sa_release(struct usb_serial *serial) { struct belkin_sa_private *priv; int i; dbg("%s", __func__); - /* stop reads and writes on all ports */ for (i = 0; i < serial->num_ports; ++i) { /* My special items, the standard routines free my urbs */ priv = usb_get_serial_port_data(serial->port[i]); diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 16a154d3b2fe..2b9eeda62bfe 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -50,7 +50,7 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port, struct file *, unsigned int, unsigned int); static void cp210x_break_ctl(struct tty_struct *, int); static int cp210x_startup(struct usb_serial *); -static void cp210x_shutdown(struct usb_serial *); +static void cp210x_disconnect(struct usb_serial *); static int debug; @@ -137,7 +137,7 @@ static struct usb_serial_driver cp210x_device = { .tiocmget = cp210x_tiocmget, .tiocmset = cp210x_tiocmset, .attach = cp210x_startup, - .shutdown = cp210x_shutdown, + .disconnect = cp210x_disconnect, }; /* Config request types */ @@ -792,7 +792,7 @@ static int cp210x_startup(struct usb_serial *serial) return 0; } -static void cp210x_shutdown(struct usb_serial *serial) +static void cp210x_disconnect(struct usb_serial *serial) { int i; diff --git a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c index 933ba913e66c..336523fd7366 100644 --- a/drivers/usb/serial/cyberjack.c +++ b/drivers/usb/serial/cyberjack.c @@ -58,7 +58,8 @@ static int debug; /* Function prototypes */ static int cyberjack_startup(struct usb_serial *serial); -static void cyberjack_shutdown(struct usb_serial *serial); +static void cyberjack_disconnect(struct usb_serial *serial); +static void cyberjack_release(struct usb_serial *serial); static int cyberjack_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp); static void cyberjack_close(struct usb_serial_port *port); @@ -94,7 +95,8 @@ static struct usb_serial_driver cyberjack_device = { .id_table = id_table, .num_ports = 1, .attach = cyberjack_startup, - .shutdown = cyberjack_shutdown, + .disconnect = cyberjack_disconnect, + .release = cyberjack_release, .open = cyberjack_open, .close = cyberjack_close, .write = cyberjack_write, @@ -148,17 +150,25 @@ static int cyberjack_startup(struct usb_serial *serial) return 0; } -static void cyberjack_shutdown(struct usb_serial *serial) +static void cyberjack_disconnect(struct usb_serial *serial) { int i; dbg("%s", __func__); - for (i = 0; i < serial->num_ports; ++i) { + for (i = 0; i < serial->num_ports; ++i) usb_kill_urb(serial->port[i]->interrupt_in_urb); +} + +static void cyberjack_release(struct usb_serial *serial) +{ + int i; + + dbg("%s", __func__); + + for (i = 0; i < serial->num_ports; ++i) { /* My special items, the standard routines free my urbs */ kfree(usb_get_serial_port_data(serial->port[i])); - usb_set_serial_port_data(serial->port[i], NULL); } } diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index 669f93848539..9734085fd2fe 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -171,7 +171,7 @@ struct cypress_buf { static int cypress_earthmate_startup(struct usb_serial *serial); static int cypress_hidcom_startup(struct usb_serial *serial); static int cypress_ca42v2_startup(struct usb_serial *serial); -static void cypress_shutdown(struct usb_serial *serial); +static void cypress_release(struct usb_serial *serial); static int cypress_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp); static void cypress_close(struct usb_serial_port *port); @@ -215,7 +215,7 @@ static struct usb_serial_driver cypress_earthmate_device = { .id_table = id_table_earthmate, .num_ports = 1, .attach = cypress_earthmate_startup, - .shutdown = cypress_shutdown, + .release = cypress_release, .open = cypress_open, .close = cypress_close, .dtr_rts = cypress_dtr_rts, @@ -242,7 +242,7 @@ static struct usb_serial_driver cypress_hidcom_device = { .id_table = id_table_cyphidcomrs232, .num_ports = 1, .attach = cypress_hidcom_startup, - .shutdown = cypress_shutdown, + .release = cypress_release, .open = cypress_open, .close = cypress_close, .dtr_rts = cypress_dtr_rts, @@ -269,7 +269,7 @@ static struct usb_serial_driver cypress_ca42v2_device = { .id_table = id_table_nokiaca42v2, .num_ports = 1, .attach = cypress_ca42v2_startup, - .shutdown = cypress_shutdown, + .release = cypress_release, .open = cypress_open, .close = cypress_close, .dtr_rts = cypress_dtr_rts, @@ -616,7 +616,7 @@ static int cypress_ca42v2_startup(struct usb_serial *serial) } /* cypress_ca42v2_startup */ -static void cypress_shutdown(struct usb_serial *serial) +static void cypress_release(struct usb_serial *serial) { struct cypress_private *priv; @@ -629,7 +629,6 @@ static void cypress_shutdown(struct usb_serial *serial) if (priv) { cypress_buf_free(priv->buf); kfree(priv); - usb_set_serial_port_data(serial->port[0], NULL); } } diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 30f5140eff03..f4808091c47c 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -460,7 +460,8 @@ static int digi_carrier_raised(struct usb_serial_port *port); static void digi_dtr_rts(struct usb_serial_port *port, int on); static int digi_startup_device(struct usb_serial *serial); static int digi_startup(struct usb_serial *serial); -static void digi_shutdown(struct usb_serial *serial); +static void digi_disconnect(struct usb_serial *serial); +static void digi_release(struct usb_serial *serial); static void digi_read_bulk_callback(struct urb *urb); static int digi_read_inb_callback(struct urb *urb); static int digi_read_oob_callback(struct urb *urb); @@ -524,7 +525,8 @@ static struct usb_serial_driver digi_acceleport_2_device = { .tiocmget = digi_tiocmget, .tiocmset = digi_tiocmset, .attach = digi_startup, - .shutdown = digi_shutdown, + .disconnect = digi_disconnect, + .release = digi_release, }; static struct usb_serial_driver digi_acceleport_4_device = { @@ -550,7 +552,8 @@ static struct usb_serial_driver digi_acceleport_4_device = { .tiocmget = digi_tiocmget, .tiocmset = digi_tiocmset, .attach = digi_startup, - .shutdown = digi_shutdown, + .disconnect = digi_disconnect, + .release = digi_release, }; @@ -1556,16 +1559,23 @@ static int digi_startup(struct usb_serial *serial) } -static void digi_shutdown(struct usb_serial *serial) +static void digi_disconnect(struct usb_serial *serial) { int i; - dbg("digi_shutdown: TOP, in_interrupt()=%ld", in_interrupt()); + dbg("digi_disconnect: TOP, in_interrupt()=%ld", in_interrupt()); /* stop reads and writes on all ports */ for (i = 0; i < serial->type->num_ports + 1; i++) { usb_kill_urb(serial->port[i]->read_urb); usb_kill_urb(serial->port[i]->write_urb); } +} + + +static void digi_release(struct usb_serial *serial) +{ + int i; + dbg("digi_release: TOP, in_interrupt()=%ld", in_interrupt()); /* free the private data structures for all ports */ /* number of regular ports + 1 for the out-of-band port */ diff --git a/drivers/usb/serial/empeg.c b/drivers/usb/serial/empeg.c index 2b141ccb0cd9..80cb3471adbe 100644 --- a/drivers/usb/serial/empeg.c +++ b/drivers/usb/serial/empeg.c @@ -90,7 +90,6 @@ static int empeg_chars_in_buffer(struct tty_struct *tty); static void empeg_throttle(struct tty_struct *tty); static void empeg_unthrottle(struct tty_struct *tty); static int empeg_startup(struct usb_serial *serial); -static void empeg_shutdown(struct usb_serial *serial); static void empeg_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios); static void empeg_write_bulk_callback(struct urb *urb); @@ -124,7 +123,6 @@ static struct usb_serial_driver empeg_device = { .throttle = empeg_throttle, .unthrottle = empeg_unthrottle, .attach = empeg_startup, - .shutdown = empeg_shutdown, .set_termios = empeg_set_termios, .write = empeg_write, .write_room = empeg_write_room, @@ -427,12 +425,6 @@ static int empeg_startup(struct usb_serial *serial) } -static void empeg_shutdown(struct usb_serial *serial) -{ - dbg("%s", __func__); -} - - static void empeg_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index fc423583eede..3dc3768ca71c 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -720,7 +720,6 @@ static const char *ftdi_chip_name[] = { /* function prototypes for a FTDI serial converter */ static int ftdi_sio_probe(struct usb_serial *serial, const struct usb_device_id *id); -static void ftdi_shutdown(struct usb_serial *serial); static int ftdi_sio_port_probe(struct usb_serial_port *port); static int ftdi_sio_port_remove(struct usb_serial_port *port); static int ftdi_open(struct tty_struct *tty, @@ -779,7 +778,6 @@ static struct usb_serial_driver ftdi_sio_device = { .ioctl = ftdi_ioctl, .set_termios = ftdi_set_termios, .break_ctl = ftdi_break_ctl, - .shutdown = ftdi_shutdown, }; @@ -1594,18 +1592,6 @@ static int ftdi_mtxorb_hack_setup(struct usb_serial *serial) return 0; } -/* ftdi_shutdown is called from usbserial:usb_serial_disconnect - * it is called when the usb device is disconnected - * - * usbserial:usb_serial_disconnect - * calls __serial_close for each open of the port - * shutdown is called then (ie ftdi_shutdown) - */ -static void ftdi_shutdown(struct usb_serial *serial) -{ - dbg("%s", __func__); -} - static void ftdi_sio_priv_release(struct kref *k) { struct ftdi_private *priv = container_of(k, struct ftdi_private, kref); diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c index 5092d6aba659..8839f1c70b7f 100644 --- a/drivers/usb/serial/garmin_gps.c +++ b/drivers/usb/serial/garmin_gps.c @@ -1475,7 +1475,7 @@ static int garmin_attach(struct usb_serial *serial) } -static void garmin_shutdown(struct usb_serial *serial) +static void garmin_disconnect(struct usb_serial *serial) { struct usb_serial_port *port = serial->port[0]; struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); @@ -1484,8 +1484,17 @@ static void garmin_shutdown(struct usb_serial *serial) usb_kill_urb(port->interrupt_in_urb); del_timer_sync(&garmin_data_p->timer); +} + + +static void garmin_release(struct usb_serial *serial) +{ + struct usb_serial_port *port = serial->port[0]; + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + + dbg("%s", __func__); + kfree(garmin_data_p); - usb_set_serial_port_data(port, NULL); } @@ -1504,7 +1513,8 @@ static struct usb_serial_driver garmin_device = { .throttle = garmin_throttle, .unthrottle = garmin_unthrottle, .attach = garmin_attach, - .shutdown = garmin_shutdown, + .disconnect = garmin_disconnect, + .release = garmin_release, .write = garmin_write, .write_room = garmin_write_room, .write_bulk_callback = garmin_write_bulk_callback, diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 8dabac1929b0..932d6241b787 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -63,7 +63,8 @@ struct usb_serial_driver usb_serial_generic_device = { .id_table = generic_device_ids, .usb_driver = &generic_driver, .num_ports = 1, - .shutdown = usb_serial_generic_shutdown, + .disconnect = usb_serial_generic_disconnect, + .release = usb_serial_generic_release, .throttle = usb_serial_generic_throttle, .unthrottle = usb_serial_generic_unthrottle, .resume = usb_serial_generic_resume, @@ -551,7 +552,7 @@ int usb_serial_handle_break(struct usb_serial_port *port) } EXPORT_SYMBOL_GPL(usb_serial_handle_break); -void usb_serial_generic_shutdown(struct usb_serial *serial) +void usb_serial_generic_disconnect(struct usb_serial *serial) { int i; @@ -562,3 +563,7 @@ void usb_serial_generic_shutdown(struct usb_serial *serial) generic_cleanup(serial->port[i]); } +void usb_serial_generic_release(struct usb_serial *serial) +{ + dbg("%s", __func__); +} diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index 53ef5996e33d..0191693625d6 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -224,7 +224,8 @@ static int edge_tiocmget(struct tty_struct *tty, struct file *file); static int edge_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear); static int edge_startup(struct usb_serial *serial); -static void edge_shutdown(struct usb_serial *serial); +static void edge_disconnect(struct usb_serial *serial); +static void edge_release(struct usb_serial *serial); #include "io_tables.h" /* all of the devices that this driver supports */ @@ -3193,21 +3194,16 @@ static int edge_startup(struct usb_serial *serial) /**************************************************************************** - * edge_shutdown + * edge_disconnect * This function is called whenever the device is removed from the usb bus. ****************************************************************************/ -static void edge_shutdown(struct usb_serial *serial) +static void edge_disconnect(struct usb_serial *serial) { struct edgeport_serial *edge_serial = usb_get_serial_data(serial); - int i; dbg("%s", __func__); /* stop reads and writes on all ports */ - for (i = 0; i < serial->num_ports; ++i) { - kfree(usb_get_serial_port_data(serial->port[i])); - usb_set_serial_port_data(serial->port[i], NULL); - } /* free up our endpoint stuff */ if (edge_serial->is_epic) { usb_kill_urb(edge_serial->interrupt_read_urb); @@ -3218,9 +3214,24 @@ static void edge_shutdown(struct usb_serial *serial) usb_free_urb(edge_serial->read_urb); kfree(edge_serial->bulk_in_buffer); } +} + + +/**************************************************************************** + * edge_release + * This function is called when the device structure is deallocated. + ****************************************************************************/ +static void edge_release(struct usb_serial *serial) +{ + struct edgeport_serial *edge_serial = usb_get_serial_data(serial); + int i; + + dbg("%s", __func__); + + for (i = 0; i < serial->num_ports; ++i) + kfree(usb_get_serial_port_data(serial->port[i])); kfree(edge_serial); - usb_set_serial_data(serial, NULL); } diff --git a/drivers/usb/serial/io_tables.h b/drivers/usb/serial/io_tables.h index 7eb9d67b81b6..9241d3147513 100644 --- a/drivers/usb/serial/io_tables.h +++ b/drivers/usb/serial/io_tables.h @@ -117,7 +117,8 @@ static struct usb_serial_driver edgeport_2port_device = { .throttle = edge_throttle, .unthrottle = edge_unthrottle, .attach = edge_startup, - .shutdown = edge_shutdown, + .disconnect = edge_disconnect, + .release = edge_release, .ioctl = edge_ioctl, .set_termios = edge_set_termios, .tiocmget = edge_tiocmget, @@ -145,7 +146,8 @@ static struct usb_serial_driver edgeport_4port_device = { .throttle = edge_throttle, .unthrottle = edge_unthrottle, .attach = edge_startup, - .shutdown = edge_shutdown, + .disconnect = edge_disconnect, + .release = edge_release, .ioctl = edge_ioctl, .set_termios = edge_set_termios, .tiocmget = edge_tiocmget, @@ -173,7 +175,8 @@ static struct usb_serial_driver edgeport_8port_device = { .throttle = edge_throttle, .unthrottle = edge_unthrottle, .attach = edge_startup, - .shutdown = edge_shutdown, + .disconnect = edge_disconnect, + .release = edge_release, .ioctl = edge_ioctl, .set_termios = edge_set_termios, .tiocmget = edge_tiocmget, @@ -200,7 +203,8 @@ static struct usb_serial_driver epic_device = { .throttle = edge_throttle, .unthrottle = edge_unthrottle, .attach = edge_startup, - .shutdown = edge_shutdown, + .disconnect = edge_disconnect, + .release = edge_release, .ioctl = edge_ioctl, .set_termios = edge_set_termios, .tiocmget = edge_tiocmget, diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index db964db42d3c..e8bc42f92e79 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -2663,7 +2663,7 @@ cleanup: return -ENOMEM; } -static void edge_shutdown(struct usb_serial *serial) +static void edge_disconnect(struct usb_serial *serial) { int i; struct edgeport_port *edge_port; @@ -2673,12 +2673,22 @@ static void edge_shutdown(struct usb_serial *serial) for (i = 0; i < serial->num_ports; ++i) { edge_port = usb_get_serial_port_data(serial->port[i]); edge_remove_sysfs_attrs(edge_port->port); + } +} + +static void edge_release(struct usb_serial *serial) +{ + int i; + struct edgeport_port *edge_port; + + dbg("%s", __func__); + + for (i = 0; i < serial->num_ports; ++i) { + edge_port = usb_get_serial_port_data(serial->port[i]); edge_buf_free(edge_port->ep_out_buf); kfree(edge_port); - usb_set_serial_port_data(serial->port[i], NULL); } kfree(usb_get_serial_data(serial)); - usb_set_serial_data(serial, NULL); } @@ -2915,7 +2925,8 @@ static struct usb_serial_driver edgeport_1port_device = { .throttle = edge_throttle, .unthrottle = edge_unthrottle, .attach = edge_startup, - .shutdown = edge_shutdown, + .disconnect = edge_disconnect, + .release = edge_release, .port_probe = edge_create_sysfs_attrs, .ioctl = edge_ioctl, .set_termios = edge_set_termios, @@ -2944,7 +2955,8 @@ static struct usb_serial_driver edgeport_2port_device = { .throttle = edge_throttle, .unthrottle = edge_unthrottle, .attach = edge_startup, - .shutdown = edge_shutdown, + .disconnect = edge_disconnect, + .release = edge_release, .port_probe = edge_create_sysfs_attrs, .ioctl = edge_ioctl, .set_termios = edge_set_termios, diff --git a/drivers/usb/serial/ipaq.c b/drivers/usb/serial/ipaq.c index c610a99fa477..2545d45ce16f 100644 --- a/drivers/usb/serial/ipaq.c +++ b/drivers/usb/serial/ipaq.c @@ -79,7 +79,6 @@ static int ipaq_open(struct tty_struct *tty, static void ipaq_close(struct usb_serial_port *port); static int ipaq_calc_num_ports(struct usb_serial *serial); static int ipaq_startup(struct usb_serial *serial); -static void ipaq_shutdown(struct usb_serial *serial); static int ipaq_write(struct tty_struct *tty, struct usb_serial_port *port, const unsigned char *buf, int count); static int ipaq_write_bulk(struct usb_serial_port *port, @@ -576,7 +575,6 @@ static struct usb_serial_driver ipaq_device = { .close = ipaq_close, .attach = ipaq_startup, .calc_num_ports = ipaq_calc_num_ports, - .shutdown = ipaq_shutdown, .write = ipaq_write, .write_room = ipaq_write_room, .chars_in_buffer = ipaq_chars_in_buffer, @@ -990,11 +988,6 @@ static int ipaq_startup(struct usb_serial *serial) return usb_reset_configuration(serial->dev); } -static void ipaq_shutdown(struct usb_serial *serial) -{ - dbg("%s", __func__); -} - static int __init ipaq_init(void) { int retval; diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c index 76a3cc327bb9..96873a7a32b0 100644 --- a/drivers/usb/serial/iuu_phoenix.c +++ b/drivers/usb/serial/iuu_phoenix.c @@ -121,8 +121,8 @@ static int iuu_startup(struct usb_serial *serial) return 0; } -/* Shutdown function */ -static void iuu_shutdown(struct usb_serial *serial) +/* Release function */ +static void iuu_release(struct usb_serial *serial) { struct usb_serial_port *port = serial->port[0]; struct iuu_private *priv = usb_get_serial_port_data(port); @@ -1202,7 +1202,7 @@ static struct usb_serial_driver iuu_device = { .tiocmset = iuu_tiocmset, .set_termios = iuu_set_termios, .attach = iuu_startup, - .shutdown = iuu_shutdown, + .release = iuu_release, }; static int __init iuu_init(void) diff --git a/drivers/usb/serial/keyspan.c b/drivers/usb/serial/keyspan.c index f1195a98f316..2594b8743d3f 100644 --- a/drivers/usb/serial/keyspan.c +++ b/drivers/usb/serial/keyspan.c @@ -2689,7 +2689,7 @@ static int keyspan_startup(struct usb_serial *serial) return 0; } -static void keyspan_shutdown(struct usb_serial *serial) +static void keyspan_disconnect(struct usb_serial *serial) { int i, j; struct usb_serial_port *port; @@ -2729,6 +2729,17 @@ static void keyspan_shutdown(struct usb_serial *serial) usb_free_urb(p_priv->out_urbs[j]); } } +} + +static void keyspan_release(struct usb_serial *serial) +{ + int i; + struct usb_serial_port *port; + struct keyspan_serial_private *s_priv; + + dbg("%s", __func__); + + s_priv = usb_get_serial_data(serial); /* dbg("Freeing serial->private."); */ kfree(s_priv); diff --git a/drivers/usb/serial/keyspan.h b/drivers/usb/serial/keyspan.h index 0d4569b60768..3107ed15af64 100644 --- a/drivers/usb/serial/keyspan.h +++ b/drivers/usb/serial/keyspan.h @@ -41,7 +41,8 @@ static int keyspan_open (struct tty_struct *tty, static void keyspan_close (struct usb_serial_port *port); static void keyspan_dtr_rts (struct usb_serial_port *port, int on); static int keyspan_startup (struct usb_serial *serial); -static void keyspan_shutdown (struct usb_serial *serial); +static void keyspan_disconnect (struct usb_serial *serial); +static void keyspan_release (struct usb_serial *serial); static int keyspan_write_room (struct tty_struct *tty); static int keyspan_write (struct tty_struct *tty, @@ -569,7 +570,8 @@ static struct usb_serial_driver keyspan_1port_device = { .tiocmget = keyspan_tiocmget, .tiocmset = keyspan_tiocmset, .attach = keyspan_startup, - .shutdown = keyspan_shutdown, + .disconnect = keyspan_disconnect, + .release = keyspan_release, }; static struct usb_serial_driver keyspan_2port_device = { @@ -590,7 +592,8 @@ static struct usb_serial_driver keyspan_2port_device = { .tiocmget = keyspan_tiocmget, .tiocmset = keyspan_tiocmset, .attach = keyspan_startup, - .shutdown = keyspan_shutdown, + .disconnect = keyspan_disconnect, + .release = keyspan_release, }; static struct usb_serial_driver keyspan_4port_device = { @@ -611,7 +614,8 @@ static struct usb_serial_driver keyspan_4port_device = { .tiocmget = keyspan_tiocmget, .tiocmset = keyspan_tiocmset, .attach = keyspan_startup, - .shutdown = keyspan_shutdown, + .disconnect = keyspan_disconnect, + .release = keyspan_release, }; #endif diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index ab769dbea1b3..d0b12e40c2b1 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -809,7 +809,7 @@ static int keyspan_pda_startup(struct usb_serial *serial) return 0; } -static void keyspan_pda_shutdown(struct usb_serial *serial) +static void keyspan_pda_release(struct usb_serial *serial) { dbg("%s", __func__); @@ -869,7 +869,7 @@ static struct usb_serial_driver keyspan_pda_device = { .tiocmget = keyspan_pda_tiocmget, .tiocmset = keyspan_pda_tiocmset, .attach = keyspan_pda_startup, - .shutdown = keyspan_pda_shutdown, + .release = keyspan_pda_release, }; diff --git a/drivers/usb/serial/kl5kusb105.c b/drivers/usb/serial/kl5kusb105.c index fa817c66b3e8..0f44bb8e8d4f 100644 --- a/drivers/usb/serial/kl5kusb105.c +++ b/drivers/usb/serial/kl5kusb105.c @@ -73,7 +73,8 @@ static int debug; * Function prototypes */ static int klsi_105_startup(struct usb_serial *serial); -static void klsi_105_shutdown(struct usb_serial *serial); +static void klsi_105_disconnect(struct usb_serial *serial); +static void klsi_105_release(struct usb_serial *serial); static int klsi_105_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp); static void klsi_105_close(struct usb_serial_port *port); @@ -131,7 +132,8 @@ static struct usb_serial_driver kl5kusb105d_device = { .tiocmget = klsi_105_tiocmget, .tiocmset = klsi_105_tiocmset, .attach = klsi_105_startup, - .shutdown = klsi_105_shutdown, + .disconnect = klsi_105_disconnect, + .release = klsi_105_release, .throttle = klsi_105_throttle, .unthrottle = klsi_105_unthrottle, }; @@ -315,7 +317,7 @@ err_cleanup: } /* klsi_105_startup */ -static void klsi_105_shutdown(struct usb_serial *serial) +static void klsi_105_disconnect(struct usb_serial *serial) { int i; @@ -325,33 +327,36 @@ static void klsi_105_shutdown(struct usb_serial *serial) for (i = 0; i < serial->num_ports; ++i) { struct klsi_105_private *priv = usb_get_serial_port_data(serial->port[i]); - unsigned long flags; if (priv) { /* kill our write urb pool */ int j; struct urb **write_urbs = priv->write_urb_pool; - spin_lock_irqsave(&priv->lock, flags); for (j = 0; j < NUM_URBS; j++) { if (write_urbs[j]) { - /* FIXME - uncomment the following - * usb_kill_urb call when the host - * controllers get fixed to set - * urb->dev = NULL after the urb is - * finished. Otherwise this call - * oopses. */ - /* usb_kill_urb(write_urbs[j]); */ - kfree(write_urbs[j]->transfer_buffer); + usb_kill_urb(write_urbs[j]); usb_free_urb(write_urbs[j]); } } - spin_unlock_irqrestore(&priv->lock, flags); - kfree(priv); - usb_set_serial_port_data(serial->port[i], NULL); } } -} /* klsi_105_shutdown */ +} /* klsi_105_disconnect */ + + +static void klsi_105_release(struct usb_serial *serial) +{ + int i; + + dbg("%s", __func__); + + for (i = 0; i < serial->num_ports; ++i) { + struct klsi_105_private *priv = + usb_get_serial_port_data(serial->port[i]); + + kfree(priv); + } +} /* klsi_105_release */ static int klsi_105_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp) diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c index 6b570498287f..6db0e561f680 100644 --- a/drivers/usb/serial/kobil_sct.c +++ b/drivers/usb/serial/kobil_sct.c @@ -69,7 +69,7 @@ static int debug; /* Function prototypes */ static int kobil_startup(struct usb_serial *serial); -static void kobil_shutdown(struct usb_serial *serial); +static void kobil_release(struct usb_serial *serial); static int kobil_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp); static void kobil_close(struct usb_serial_port *port); @@ -117,7 +117,7 @@ static struct usb_serial_driver kobil_device = { .id_table = id_table, .num_ports = 1, .attach = kobil_startup, - .shutdown = kobil_shutdown, + .release = kobil_release, .ioctl = kobil_ioctl, .set_termios = kobil_set_termios, .tiocmget = kobil_tiocmget, @@ -201,17 +201,13 @@ static int kobil_startup(struct usb_serial *serial) } -static void kobil_shutdown(struct usb_serial *serial) +static void kobil_release(struct usb_serial *serial) { int i; dbg("%s - port %d", __func__, serial->port[0]->number); - for (i = 0; i < serial->num_ports; ++i) { - while (serial->port[i]->port.count > 0) - kobil_close(serial->port[i]); + for (i = 0; i < serial->num_ports; ++i) kfree(usb_get_serial_port_data(serial->port[i])); - usb_set_serial_port_data(serial->port[i], NULL); - } } diff --git a/drivers/usb/serial/mct_u232.c b/drivers/usb/serial/mct_u232.c index 873795548fc0..d8825e159aa5 100644 --- a/drivers/usb/serial/mct_u232.c +++ b/drivers/usb/serial/mct_u232.c @@ -92,7 +92,7 @@ static int debug; * Function prototypes */ static int mct_u232_startup(struct usb_serial *serial); -static void mct_u232_shutdown(struct usb_serial *serial); +static void mct_u232_release(struct usb_serial *serial); static int mct_u232_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp); static void mct_u232_close(struct usb_serial_port *port); @@ -149,7 +149,7 @@ static struct usb_serial_driver mct_u232_device = { .tiocmget = mct_u232_tiocmget, .tiocmset = mct_u232_tiocmset, .attach = mct_u232_startup, - .shutdown = mct_u232_shutdown, + .release = mct_u232_release, }; @@ -407,7 +407,7 @@ static int mct_u232_startup(struct usb_serial *serial) } /* mct_u232_startup */ -static void mct_u232_shutdown(struct usb_serial *serial) +static void mct_u232_release(struct usb_serial *serial) { struct mct_u232_private *priv; int i; @@ -417,12 +417,9 @@ static void mct_u232_shutdown(struct usb_serial *serial) for (i = 0; i < serial->num_ports; ++i) { /* My special items, the standard routines free my urbs */ priv = usb_get_serial_port_data(serial->port[i]); - if (priv) { - usb_set_serial_port_data(serial->port[i], NULL); - kfree(priv); - } + kfree(priv); } -} /* mct_u232_shutdown */ +} /* mct_u232_release */ static int mct_u232_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp) diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index 9e1a013ee7f6..bfc5ce000ef9 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -1521,19 +1521,16 @@ static int mos7720_startup(struct usb_serial *serial) return 0; } -static void mos7720_shutdown(struct usb_serial *serial) +static void mos7720_release(struct usb_serial *serial) { int i; /* free private structure allocated for serial port */ - for (i = 0; i < serial->num_ports; ++i) { + for (i = 0; i < serial->num_ports; ++i) kfree(usb_get_serial_port_data(serial->port[i])); - usb_set_serial_port_data(serial->port[i], NULL); - } /* free private structure allocated for serial device */ kfree(usb_get_serial_data(serial)); - usb_set_serial_data(serial, NULL); } static struct usb_driver usb_driver = { @@ -1558,7 +1555,7 @@ static struct usb_serial_driver moschip7720_2port_driver = { .throttle = mos7720_throttle, .unthrottle = mos7720_unthrottle, .attach = mos7720_startup, - .shutdown = mos7720_shutdown, + .release = mos7720_release, .ioctl = mos7720_ioctl, .set_termios = mos7720_set_termios, .write = mos7720_write, diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 5fe9fe3df772..c40f95c1951c 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -2633,16 +2633,16 @@ error: } /**************************************************************************** - * mos7840_shutdown + * mos7840_disconnect * This function is called whenever the device is removed from the usb bus. ****************************************************************************/ -static void mos7840_shutdown(struct usb_serial *serial) +static void mos7840_disconnect(struct usb_serial *serial) { int i; unsigned long flags; struct moschip_port *mos7840_port; - dbg("%s", " shutdown :entering.........."); + dbg("%s", " disconnect :entering.........."); if (!serial) { dbg("%s", "Invalid Handler"); @@ -2662,11 +2662,42 @@ static void mos7840_shutdown(struct usb_serial *serial) mos7840_port->zombie = 1; spin_unlock_irqrestore(&mos7840_port->pool_lock, flags); usb_kill_urb(mos7840_port->control_urb); + } + } + + dbg("%s", "Thank u :: "); + +} + +/**************************************************************************** + * mos7840_release + * This function is called when the usb_serial structure is freed. + ****************************************************************************/ + +static void mos7840_release(struct usb_serial *serial) +{ + int i; + struct moschip_port *mos7840_port; + dbg("%s", " release :entering.........."); + + if (!serial) { + dbg("%s", "Invalid Handler"); + return; + } + + /* check for the ports to be closed,close the ports and disconnect */ + + /* free private structure allocated for serial port * + * stop reads and writes on all ports */ + + for (i = 0; i < serial->num_ports; ++i) { + mos7840_port = mos7840_get_port_private(serial->port[i]); + dbg("mos7840_port %d = %p", i, mos7840_port); + if (mos7840_port) { kfree(mos7840_port->ctrl_buf); kfree(mos7840_port->dr); kfree(mos7840_port); } - mos7840_set_port_private(serial->port[i], NULL); } dbg("%s", "Thank u :: "); @@ -2707,7 +2738,8 @@ static struct usb_serial_driver moschip7840_4port_device = { .tiocmget = mos7840_tiocmget, .tiocmset = mos7840_tiocmset, .attach = mos7840_startup, - .shutdown = mos7840_shutdown, + .disconnect = mos7840_disconnect, + .release = mos7840_release, .read_bulk_callback = mos7840_bulk_in_callback, .read_int_callback = mos7840_interrupt_callback, }; diff --git a/drivers/usb/serial/omninet.c b/drivers/usb/serial/omninet.c index 1104617334f5..56857ddbd70b 100644 --- a/drivers/usb/serial/omninet.c +++ b/drivers/usb/serial/omninet.c @@ -72,7 +72,8 @@ static void omninet_write_bulk_callback(struct urb *urb); static int omninet_write(struct tty_struct *tty, struct usb_serial_port *port, const unsigned char *buf, int count); static int omninet_write_room(struct tty_struct *tty); -static void omninet_shutdown(struct usb_serial *serial); +static void omninet_disconnect(struct usb_serial *serial); +static void omninet_release(struct usb_serial *serial); static int omninet_attach(struct usb_serial *serial); static struct usb_device_id id_table[] = { @@ -108,7 +109,8 @@ static struct usb_serial_driver zyxel_omninet_device = { .write_room = omninet_write_room, .read_bulk_callback = omninet_read_bulk_callback, .write_bulk_callback = omninet_write_bulk_callback, - .shutdown = omninet_shutdown, + .disconnect = omninet_disconnect, + .release = omninet_release, }; @@ -345,13 +347,22 @@ static void omninet_write_bulk_callback(struct urb *urb) } -static void omninet_shutdown(struct usb_serial *serial) +static void omninet_disconnect(struct usb_serial *serial) { struct usb_serial_port *wport = serial->port[1]; - struct usb_serial_port *port = serial->port[0]; + dbg("%s", __func__); usb_kill_urb(wport->write_urb); +} + + +static void omninet_release(struct usb_serial *serial) +{ + struct usb_serial_port *port = serial->port[0]; + + dbg("%s", __func__); + kfree(usb_get_serial_port_data(port)); } diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c index c20480aa9755..336bba79ad32 100644 --- a/drivers/usb/serial/opticon.c +++ b/drivers/usb/serial/opticon.c @@ -463,7 +463,7 @@ error: return retval; } -static void opticon_shutdown(struct usb_serial *serial) +static void opticon_disconnect(struct usb_serial *serial) { struct opticon_private *priv = usb_get_serial_data(serial); @@ -471,9 +471,16 @@ static void opticon_shutdown(struct usb_serial *serial) usb_kill_urb(priv->bulk_read_urb); usb_free_urb(priv->bulk_read_urb); +} + +static void opticon_release(struct usb_serial *serial) +{ + struct opticon_private *priv = usb_get_serial_data(serial); + + dbg("%s", __func__); + kfree(priv->bulk_in_buffer); kfree(priv); - usb_set_serial_data(serial, NULL); } static int opticon_suspend(struct usb_interface *intf, pm_message_t message) @@ -524,7 +531,8 @@ static struct usb_serial_driver opticon_device = { .close = opticon_close, .write = opticon_write, .write_room = opticon_write_room, - .shutdown = opticon_shutdown, + .disconnect = opticon_disconnect, + .release = opticon_release, .throttle = opticon_throttle, .unthrottle = opticon_unthrottle, .ioctl = opticon_ioctl, diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index a38971cc3731..575816e6ba37 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -51,7 +51,8 @@ static void option_close(struct usb_serial_port *port); static void option_dtr_rts(struct usb_serial_port *port, int on); static int option_startup(struct usb_serial *serial); -static void option_shutdown(struct usb_serial *serial); +static void option_disconnect(struct usb_serial *serial); +static void option_release(struct usb_serial *serial); static int option_write_room(struct tty_struct *tty); static void option_instat_callback(struct urb *urb); @@ -568,7 +569,8 @@ static struct usb_serial_driver option_1port_device = { .tiocmget = option_tiocmget, .tiocmset = option_tiocmset, .attach = option_startup, - .shutdown = option_shutdown, + .disconnect = option_disconnect, + .release = option_release, .read_int_callback = option_instat_callback, .suspend = option_suspend, .resume = option_resume, @@ -1149,7 +1151,14 @@ static void stop_read_write_urbs(struct usb_serial *serial) } } -static void option_shutdown(struct usb_serial *serial) +static void option_disconnect(struct usb_serial *serial) +{ + dbg("%s", __func__); + + stop_read_write_urbs(serial); +} + +static void option_release(struct usb_serial *serial) { int i, j; struct usb_serial_port *port; @@ -1157,8 +1166,6 @@ static void option_shutdown(struct usb_serial *serial) dbg("%s", __func__); - stop_read_write_urbs(serial); - /* Now free them */ for (i = 0; i < serial->num_ports; ++i) { port = serial->port[i]; diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c index 7de54781fe61..3cece27325e7 100644 --- a/drivers/usb/serial/oti6858.c +++ b/drivers/usb/serial/oti6858.c @@ -159,7 +159,7 @@ static int oti6858_tiocmget(struct tty_struct *tty, struct file *file); static int oti6858_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear); static int oti6858_startup(struct usb_serial *serial); -static void oti6858_shutdown(struct usb_serial *serial); +static void oti6858_release(struct usb_serial *serial); /* functions operating on buffers */ static struct oti6858_buf *oti6858_buf_alloc(unsigned int size); @@ -194,7 +194,7 @@ static struct usb_serial_driver oti6858_device = { .write_room = oti6858_write_room, .chars_in_buffer = oti6858_chars_in_buffer, .attach = oti6858_startup, - .shutdown = oti6858_shutdown, + .release = oti6858_release, }; struct oti6858_private { @@ -782,7 +782,7 @@ static int oti6858_ioctl(struct tty_struct *tty, struct file *file, } -static void oti6858_shutdown(struct usb_serial *serial) +static void oti6858_release(struct usb_serial *serial) { struct oti6858_private *priv; int i; @@ -794,7 +794,6 @@ static void oti6858_shutdown(struct usb_serial *serial) if (priv) { oti6858_buf_free(priv->buf); kfree(priv); - usb_set_serial_port_data(serial->port[i], NULL); } } } diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 6357b57f628c..ec6c132a25b5 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -878,7 +878,7 @@ static void pl2303_break_ctl(struct tty_struct *tty, int break_state) dbg("%s - error sending break = %d", __func__, result); } -static void pl2303_shutdown(struct usb_serial *serial) +static void pl2303_release(struct usb_serial *serial) { int i; struct pl2303_private *priv; @@ -890,7 +890,6 @@ static void pl2303_shutdown(struct usb_serial *serial) if (priv) { pl2303_buf_free(priv->buf); kfree(priv); - usb_set_serial_port_data(serial->port[i], NULL); } } } @@ -1123,7 +1122,7 @@ static struct usb_serial_driver pl2303_device = { .write_room = pl2303_write_room, .chars_in_buffer = pl2303_chars_in_buffer, .attach = pl2303_startup, - .shutdown = pl2303_shutdown, + .release = pl2303_release, }; static int __init pl2303_init(void) diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index a88cde950161..032f7aeb40a4 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -814,7 +814,7 @@ static int sierra_startup(struct usb_serial *serial) return 0; } -static void sierra_shutdown(struct usb_serial *serial) +static void sierra_disconnect(struct usb_serial *serial) { int i; struct usb_serial_port *port; @@ -853,7 +853,7 @@ static struct usb_serial_driver sierra_device = { .tiocmget = sierra_tiocmget, .tiocmset = sierra_tiocmset, .attach = sierra_startup, - .shutdown = sierra_shutdown, + .disconnect = sierra_disconnect, .read_int_callback = sierra_instat_callback, }; diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c index 8f7ed8f13996..3c249d8e8b8e 100644 --- a/drivers/usb/serial/spcp8x5.c +++ b/drivers/usb/serial/spcp8x5.c @@ -356,7 +356,7 @@ cleanup: } /* call when the device plug out. free all the memory alloced by probe */ -static void spcp8x5_shutdown(struct usb_serial *serial) +static void spcp8x5_release(struct usb_serial *serial) { int i; struct spcp8x5_private *priv; @@ -366,7 +366,6 @@ static void spcp8x5_shutdown(struct usb_serial *serial) if (priv) { free_ringbuf(priv->buf); kfree(priv); - usb_set_serial_port_data(serial->port[i] , NULL); } } } @@ -1020,7 +1019,7 @@ static struct usb_serial_driver spcp8x5_device = { .write_bulk_callback = spcp8x5_write_bulk_callback, .chars_in_buffer = spcp8x5_chars_in_buffer, .attach = spcp8x5_startup, - .shutdown = spcp8x5_shutdown, + .release = spcp8x5_release, }; static int __init spcp8x5_init(void) diff --git a/drivers/usb/serial/symbolserial.c b/drivers/usb/serial/symbolserial.c index 8b07ebc6baeb..6157fac9366b 100644 --- a/drivers/usb/serial/symbolserial.c +++ b/drivers/usb/serial/symbolserial.c @@ -267,7 +267,7 @@ error: return retval; } -static void symbol_shutdown(struct usb_serial *serial) +static void symbol_disconnect(struct usb_serial *serial) { struct symbol_private *priv = usb_get_serial_data(serial); @@ -275,9 +275,16 @@ static void symbol_shutdown(struct usb_serial *serial) usb_kill_urb(priv->int_urb); usb_free_urb(priv->int_urb); +} + +static void symbol_release(struct usb_serial *serial) +{ + struct symbol_private *priv = usb_get_serial_data(serial); + + dbg("%s", __func__); + kfree(priv->int_buffer); kfree(priv); - usb_set_serial_data(serial, NULL); } static struct usb_driver symbol_driver = { @@ -299,7 +306,8 @@ static struct usb_serial_driver symbol_device = { .attach = symbol_startup, .open = symbol_open, .close = symbol_close, - .shutdown = symbol_shutdown, + .disconnect = symbol_disconnect, + .release = symbol_release, .throttle = symbol_throttle, .unthrottle = symbol_unthrottle, }; diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index 42cb04c403be..991d8232e376 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -97,7 +97,7 @@ struct ti_device { /* Function Declarations */ static int ti_startup(struct usb_serial *serial); -static void ti_shutdown(struct usb_serial *serial); +static void ti_release(struct usb_serial *serial); static int ti_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *file); static void ti_close(struct usb_serial_port *port); @@ -230,7 +230,7 @@ static struct usb_serial_driver ti_1port_device = { .id_table = ti_id_table_3410, .num_ports = 1, .attach = ti_startup, - .shutdown = ti_shutdown, + .release = ti_release, .open = ti_open, .close = ti_close, .write = ti_write, @@ -258,7 +258,7 @@ static struct usb_serial_driver ti_2port_device = { .id_table = ti_id_table_5052, .num_ports = 2, .attach = ti_startup, - .shutdown = ti_shutdown, + .release = ti_release, .open = ti_open, .close = ti_close, .write = ti_write, @@ -473,7 +473,7 @@ free_tdev: } -static void ti_shutdown(struct usb_serial *serial) +static void ti_release(struct usb_serial *serial) { int i; struct ti_device *tdev = usb_get_serial_data(serial); @@ -486,12 +486,10 @@ static void ti_shutdown(struct usb_serial *serial) if (tport) { ti_buf_free(tport->tp_write_buf); kfree(tport); - usb_set_serial_port_data(serial->port[i], NULL); } } kfree(tdev); - usb_set_serial_data(serial, NULL); } diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index da890f030fac..d595aa5586a7 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -141,6 +141,14 @@ static void destroy_serial(struct kref *kref) if (serial->minor != SERIAL_TTY_NO_MINOR) return_serial(serial); + serial->type->release(serial); + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (port) + put_device(&port->dev); + } + /* If this is a "fake" port, we have to clean it up here, as it will * not get cleaned up in port_release() as it was never registered with * the driver core */ @@ -148,9 +156,8 @@ static void destroy_serial(struct kref *kref) for (i = serial->num_ports; i < serial->num_port_pointers; ++i) { port = serial->port[i]; - if (!port) - continue; - port_free(port); + if (port) + port_free(port); } } @@ -1118,10 +1125,6 @@ void usb_serial_disconnect(struct usb_interface *interface) serial->disconnected = 1; mutex_unlock(&serial->disc_mutex); - /* Unfortunately, many of the sub-drivers expect the port structures - * to exist when their shutdown method is called, so we have to go - * through this awkward two-step unregistration procedure. - */ for (i = 0; i < serial->num_ports; ++i) { port = serial->port[i]; if (port) { @@ -1153,14 +1156,7 @@ void usb_serial_disconnect(struct usb_interface *interface) } } } - serial->type->shutdown(serial); - for (i = 0; i < serial->num_ports; ++i) { - port = serial->port[i]; - if (port) { - put_device(&port->dev); - serial->port[i] = NULL; - } - } + serial->type->disconnect(serial); /* let the last holder of this object * cause it to be cleaned up */ @@ -1338,7 +1334,8 @@ static void fixup_generic(struct usb_serial_driver *device) set_to_generic_if_null(device, chars_in_buffer); set_to_generic_if_null(device, read_bulk_callback); set_to_generic_if_null(device, write_bulk_callback); - set_to_generic_if_null(device, shutdown); + set_to_generic_if_null(device, disconnect); + set_to_generic_if_null(device, release); } int usb_serial_register(struct usb_serial_driver *driver) diff --git a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c index b15f1c0e1d4a..f5d0f64dcc52 100644 --- a/drivers/usb/serial/visor.c +++ b/drivers/usb/serial/visor.c @@ -47,7 +47,7 @@ static void visor_unthrottle(struct tty_struct *tty); static int visor_probe(struct usb_serial *serial, const struct usb_device_id *id); static int visor_calc_num_ports(struct usb_serial *serial); -static void visor_shutdown(struct usb_serial *serial); +static void visor_release(struct usb_serial *serial); static void visor_write_bulk_callback(struct urb *urb); static void visor_read_bulk_callback(struct urb *urb); static void visor_read_int_callback(struct urb *urb); @@ -202,7 +202,7 @@ static struct usb_serial_driver handspring_device = { .attach = treo_attach, .probe = visor_probe, .calc_num_ports = visor_calc_num_ports, - .shutdown = visor_shutdown, + .release = visor_release, .write = visor_write, .write_room = visor_write_room, .write_bulk_callback = visor_write_bulk_callback, @@ -227,7 +227,7 @@ static struct usb_serial_driver clie_5_device = { .attach = clie_5_attach, .probe = visor_probe, .calc_num_ports = visor_calc_num_ports, - .shutdown = visor_shutdown, + .release = visor_release, .write = visor_write, .write_room = visor_write_room, .write_bulk_callback = visor_write_bulk_callback, @@ -918,7 +918,7 @@ static int clie_5_attach(struct usb_serial *serial) return generic_startup(serial); } -static void visor_shutdown(struct usb_serial *serial) +static void visor_release(struct usb_serial *serial) { struct visor_private *priv; int i; @@ -927,10 +927,7 @@ static void visor_shutdown(struct usb_serial *serial) for (i = 0; i < serial->num_ports; i++) { priv = usb_get_serial_port_data(serial->port[i]); - if (priv) { - usb_set_serial_port_data(serial->port[i], NULL); - kfree(priv); - } + kfree(priv); } } diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c index 7c7295d09f34..8d126dd7a02e 100644 --- a/drivers/usb/serial/whiteheat.c +++ b/drivers/usb/serial/whiteheat.c @@ -144,7 +144,7 @@ static int whiteheat_firmware_attach(struct usb_serial *serial); /* function prototypes for the Connect Tech WhiteHEAT serial converter */ static int whiteheat_attach(struct usb_serial *serial); -static void whiteheat_shutdown(struct usb_serial *serial); +static void whiteheat_release(struct usb_serial *serial); static int whiteheat_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp); static void whiteheat_close(struct usb_serial_port *port); @@ -189,7 +189,7 @@ static struct usb_serial_driver whiteheat_device = { .id_table = id_table_std, .num_ports = 4, .attach = whiteheat_attach, - .shutdown = whiteheat_shutdown, + .release = whiteheat_release, .open = whiteheat_open, .close = whiteheat_close, .write = whiteheat_write, @@ -617,7 +617,7 @@ no_command_buffer: } -static void whiteheat_shutdown(struct usb_serial *serial) +static void whiteheat_release(struct usb_serial *serial) { struct usb_serial_port *command_port; struct usb_serial_port *port; diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index ed4aa0fa7ed8..44801d26a37a 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -194,8 +194,10 @@ static inline void usb_set_serial_data(struct usb_serial *serial, void *data) * This will be called when the struct usb_serial structure is fully set * set up. Do any local initialization of the device, or any private * memory structure allocation at this point in time. - * @shutdown: pointer to the driver's shutdown function. This will be - * called when the device is removed from the system. + * @disconnect: pointer to the driver's disconnect function. This will be + * called when the device is unplugged or unbound from the driver. + * @release: pointer to the driver's release function. This will be called + * when the usb_serial data structure is about to be destroyed. * @usb_driver: pointer to the struct usb_driver that controls this * device. This is necessary to allow dynamic ids to be added to * the driver from sysfs. @@ -226,7 +228,8 @@ struct usb_serial_driver { int (*attach)(struct usb_serial *serial); int (*calc_num_ports) (struct usb_serial *serial); - void (*shutdown)(struct usb_serial *serial); + void (*disconnect)(struct usb_serial *serial); + void (*release)(struct usb_serial *serial); int (*port_probe)(struct usb_serial_port *port); int (*port_remove)(struct usb_serial_port *port); @@ -308,7 +311,8 @@ extern void usb_serial_generic_read_bulk_callback(struct urb *urb); extern void usb_serial_generic_write_bulk_callback(struct urb *urb); extern void usb_serial_generic_throttle(struct tty_struct *tty); extern void usb_serial_generic_unthrottle(struct tty_struct *tty); -extern void usb_serial_generic_shutdown(struct usb_serial *serial); +extern void usb_serial_generic_disconnect(struct usb_serial *serial); +extern void usb_serial_generic_release(struct usb_serial *serial); extern int usb_serial_generic_register(int debug); extern void usb_serial_generic_deregister(void); extern void usb_serial_generic_resubmit_read_urb(struct usb_serial_port *port, -- cgit v1.2.3 From 5be19a9daa2df2507adf5b4676a7db8d131cf56e Mon Sep 17 00:00:00 2001 From: Xiaochen Shen <xiaochen.shen@intel.com> Date: Thu, 4 Jun 2009 15:34:49 +0800 Subject: USB: Add Intel Langwell USB Device Controller driver Intel Langwell USB Device Controller is a High-Speed USB OTG device controller in Intel Moorestown platform. It can work in OTG device mode with Intel Langwell USB OTG transceiver driver as well as device-only mode. The number of programmable endpoints is different through controller revision. NOTE: This patch is the first version Intel Langwell USB OTG device controller driver. The bug fixing is on going for some hardware and software issues. Intel Langwell USB OTG transceiver driver and EHCI driver patches will be submitted later. Supported features: - USB OTG protocol support with Intel Langwell USB OTG transceiver driver (turn on CONFIG_USB_LANGWELL_OTG) - Support control, bulk, interrupt and isochronous endpoints (isochronous not tested) - PCI D0/D3 power management support - Link Power Management (LPM) support Tested gadget drivers: - g_file_storage - g_ether - g_zero The passed tests: - g_file_storage: USBCV Chapter 9 tests - g_file_storage: USBCV MSC tests - g_file_storage: from/to host files copying - g_ether: ping, ftp and scp files from/to host - Hotplug, with and without hubs Known issues: - g_ether: failed part of USBCV chap9 tests - LPM support not fully tested TODO: - g_ether: pass all USBCV chap9 tests - g_zero: pass usbtest tests - Stress tests on different gadget drivers - On-chip private SRAM caching support Signed-off-by: Xiaochen Shen <xiaochen.shen@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/Kconfig | 21 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/gadget_chips.h | 8 + drivers/usb/gadget/langwell_udc.c | 3373 +++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/langwell_udc.h | 228 +++ include/linux/usb/langwell_udc.h | 310 ++++ 6 files changed, 3941 insertions(+) create mode 100644 drivers/usb/gadget/langwell_udc.c create mode 100644 drivers/usb/gadget/langwell_udc.h create mode 100644 include/linux/usb/langwell_udc.h (limited to 'drivers') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index ef9d9086cddc..5d1ddf485d1e 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -474,6 +474,27 @@ config USB_GOKU default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_LANGWELL + boolean "Intel Langwell USB Device Controller" + depends on PCI + select USB_GADGET_DUALSPEED + help + Intel Langwell USB Device Controller is a High-Speed USB + On-The-Go device controller. + + The number of programmable endpoints is different through + controller revision. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "langwell_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_LANGWELL + tristate + depends on USB_GADGET_LANGWELL + default USB_GADGET + select USB_GADGET_SELECTED + # # LAST -- dummy/emulated controller diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index fc9a7dc01047..e6017e6bf6da 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o +obj-$(CONFIG_USB_LANGWELL) += langwell_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index ec6d439a2aa5..8e0e9a0b7364 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -137,6 +137,12 @@ #define gadget_is_musbhdrc(g) 0 #endif +#ifdef CONFIG_USB_GADGET_LANGWELL +#define gadget_is_langwell(g) (!strcmp("langwell_udc", (g)->name)) +#else +#define gadget_is_langwell(g) 0 +#endif + /* from Montavista kernel (?) */ #ifdef CONFIG_USB_GADGET_MPC8272 #define gadget_is_mpc8272(g) !strcmp("mpc8272_udc", (g)->name) @@ -231,6 +237,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x22; else if (gadget_is_ci13xxx(gadget)) return 0x23; + else if (gadget_is_langwell(gadget)) + return 0x24; return -ENOENT; } diff --git a/drivers/usb/gadget/langwell_udc.c b/drivers/usb/gadget/langwell_udc.c new file mode 100644 index 000000000000..6829d5961359 --- /dev/null +++ b/drivers/usb/gadget/langwell_udc.c @@ -0,0 +1,3373 @@ +/* + * Intel Langwell USB Device Controller driver + * Copyright (C) 2008-2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + + +/* #undef DEBUG */ +/* #undef VERBOSE */ + +#if defined(CONFIG_USB_LANGWELL_OTG) +#define OTG_TRANSCEIVER +#endif + + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/otg.h> +#include <linux/pm.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> + +#include "langwell_udc.h" + + +#define DRIVER_DESC "Intel Langwell USB Device Controller driver" +#define DRIVER_VERSION "16 May 2009" + +static const char driver_name[] = "langwell_udc"; +static const char driver_desc[] = DRIVER_DESC; + + +/* controller device global variable */ +static struct langwell_udc *the_controller; + +/* for endpoint 0 operations */ +static const struct usb_endpoint_descriptor +langwell_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = EP0_MAX_PKT_SIZE, +}; + + +/*-------------------------------------------------------------------------*/ +/* debugging */ + +#ifdef DEBUG +#define DBG(dev, fmt, args...) \ + pr_debug("%s %s: " fmt , driver_name, \ + pci_name(dev->pdev), ## args) +#else +#define DBG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + + +#ifdef VERBOSE +#define VDBG DBG +#else +#define VDBG(dev, fmt, args...) \ + do { } while (0) +#endif /* VERBOSE */ + + +#define ERROR(dev, fmt, args...) \ + pr_err("%s %s: " fmt , driver_name, \ + pci_name(dev->pdev), ## args) + +#define WARNING(dev, fmt, args...) \ + pr_warning("%s %s: " fmt , driver_name, \ + pci_name(dev->pdev), ## args) + +#define INFO(dev, fmt, args...) \ + pr_info("%s %s: " fmt , driver_name, \ + pci_name(dev->pdev), ## args) + + +#ifdef VERBOSE +static inline void print_all_registers(struct langwell_udc *dev) +{ + int i; + + /* Capability Registers */ + printk(KERN_DEBUG "Capability Registers (offset: " + "0x%04x, length: 0x%08x)\n", + CAP_REG_OFFSET, + (u32)sizeof(struct langwell_cap_regs)); + printk(KERN_DEBUG "caplength=0x%02x\n", + readb(&dev->cap_regs->caplength)); + printk(KERN_DEBUG "hciversion=0x%04x\n", + readw(&dev->cap_regs->hciversion)); + printk(KERN_DEBUG "hcsparams=0x%08x\n", + readl(&dev->cap_regs->hcsparams)); + printk(KERN_DEBUG "hccparams=0x%08x\n", + readl(&dev->cap_regs->hccparams)); + printk(KERN_DEBUG "dciversion=0x%04x\n", + readw(&dev->cap_regs->dciversion)); + printk(KERN_DEBUG "dccparams=0x%08x\n", + readl(&dev->cap_regs->dccparams)); + + /* Operational Registers */ + printk(KERN_DEBUG "Operational Registers (offset: " + "0x%04x, length: 0x%08x)\n", + OP_REG_OFFSET, + (u32)sizeof(struct langwell_op_regs)); + printk(KERN_DEBUG "extsts=0x%08x\n", + readl(&dev->op_regs->extsts)); + printk(KERN_DEBUG "extintr=0x%08x\n", + readl(&dev->op_regs->extintr)); + printk(KERN_DEBUG "usbcmd=0x%08x\n", + readl(&dev->op_regs->usbcmd)); + printk(KERN_DEBUG "usbsts=0x%08x\n", + readl(&dev->op_regs->usbsts)); + printk(KERN_DEBUG "usbintr=0x%08x\n", + readl(&dev->op_regs->usbintr)); + printk(KERN_DEBUG "frindex=0x%08x\n", + readl(&dev->op_regs->frindex)); + printk(KERN_DEBUG "ctrldssegment=0x%08x\n", + readl(&dev->op_regs->ctrldssegment)); + printk(KERN_DEBUG "deviceaddr=0x%08x\n", + readl(&dev->op_regs->deviceaddr)); + printk(KERN_DEBUG "endpointlistaddr=0x%08x\n", + readl(&dev->op_regs->endpointlistaddr)); + printk(KERN_DEBUG "ttctrl=0x%08x\n", + readl(&dev->op_regs->ttctrl)); + printk(KERN_DEBUG "burstsize=0x%08x\n", + readl(&dev->op_regs->burstsize)); + printk(KERN_DEBUG "txfilltuning=0x%08x\n", + readl(&dev->op_regs->txfilltuning)); + printk(KERN_DEBUG "txttfilltuning=0x%08x\n", + readl(&dev->op_regs->txttfilltuning)); + printk(KERN_DEBUG "ic_usb=0x%08x\n", + readl(&dev->op_regs->ic_usb)); + printk(KERN_DEBUG "ulpi_viewport=0x%08x\n", + readl(&dev->op_regs->ulpi_viewport)); + printk(KERN_DEBUG "configflag=0x%08x\n", + readl(&dev->op_regs->configflag)); + printk(KERN_DEBUG "portsc1=0x%08x\n", + readl(&dev->op_regs->portsc1)); + printk(KERN_DEBUG "devlc=0x%08x\n", + readl(&dev->op_regs->devlc)); + printk(KERN_DEBUG "otgsc=0x%08x\n", + readl(&dev->op_regs->otgsc)); + printk(KERN_DEBUG "usbmode=0x%08x\n", + readl(&dev->op_regs->usbmode)); + printk(KERN_DEBUG "endptnak=0x%08x\n", + readl(&dev->op_regs->endptnak)); + printk(KERN_DEBUG "endptnaken=0x%08x\n", + readl(&dev->op_regs->endptnaken)); + printk(KERN_DEBUG "endptsetupstat=0x%08x\n", + readl(&dev->op_regs->endptsetupstat)); + printk(KERN_DEBUG "endptprime=0x%08x\n", + readl(&dev->op_regs->endptprime)); + printk(KERN_DEBUG "endptflush=0x%08x\n", + readl(&dev->op_regs->endptflush)); + printk(KERN_DEBUG "endptstat=0x%08x\n", + readl(&dev->op_regs->endptstat)); + printk(KERN_DEBUG "endptcomplete=0x%08x\n", + readl(&dev->op_regs->endptcomplete)); + + for (i = 0; i < dev->ep_max / 2; i++) { + printk(KERN_DEBUG "endptctrl[%d]=0x%08x\n", + i, readl(&dev->op_regs->endptctrl[i])); + } +} +#endif /* VERBOSE */ + + +/*-------------------------------------------------------------------------*/ + +#define DIR_STRING(bAddress) (((bAddress) & USB_DIR_IN) ? "in" : "out") + +#define is_in(ep) (((ep)->ep_num == 0) ? ((ep)->dev->ep0_dir == \ + USB_DIR_IN) : ((ep)->desc->bEndpointAddress \ + & USB_DIR_IN) == USB_DIR_IN) + + +#ifdef DEBUG +static char *type_string(u8 bmAttributes) +{ + switch ((bmAttributes) & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + return "bulk"; + case USB_ENDPOINT_XFER_ISOC: + return "iso"; + case USB_ENDPOINT_XFER_INT: + return "int"; + }; + + return "control"; +} +#endif + + +/* configure endpoint control registers */ +static void ep_reset(struct langwell_ep *ep, unsigned char ep_num, + unsigned char is_in, unsigned char ep_type) +{ + struct langwell_udc *dev; + u32 endptctrl; + + dev = ep->dev; + VDBG(dev, "---> %s()\n", __func__); + + endptctrl = readl(&dev->op_regs->endptctrl[ep_num]); + if (is_in) { /* TX */ + if (ep_num) + endptctrl |= EPCTRL_TXR; + endptctrl |= EPCTRL_TXE; + endptctrl |= ep_type << EPCTRL_TXT_SHIFT; + } else { /* RX */ + if (ep_num) + endptctrl |= EPCTRL_RXR; + endptctrl |= EPCTRL_RXE; + endptctrl |= ep_type << EPCTRL_RXT_SHIFT; + } + + writel(endptctrl, &dev->op_regs->endptctrl[ep_num]); + + VDBG(dev, "<--- %s()\n", __func__); +} + + +/* reset ep0 dQH and endptctrl */ +static void ep0_reset(struct langwell_udc *dev) +{ + struct langwell_ep *ep; + int i; + + VDBG(dev, "---> %s()\n", __func__); + + /* ep0 in and out */ + for (i = 0; i < 2; i++) { + ep = &dev->ep[i]; + ep->dev = dev; + + /* ep0 dQH */ + ep->dqh = &dev->ep_dqh[i]; + + /* configure ep0 endpoint capabilities in dQH */ + ep->dqh->dqh_ios = 1; + ep->dqh->dqh_mpl = EP0_MAX_PKT_SIZE; + + /* FIXME: enable ep0-in HW zero length termination select */ + if (is_in(ep)) + ep->dqh->dqh_zlt = 0; + ep->dqh->dqh_mult = 0; + + /* configure ep0 control registers */ + ep_reset(&dev->ep[0], 0, i, USB_ENDPOINT_XFER_CONTROL); + } + + VDBG(dev, "<--- %s()\n", __func__); + return; +} + + +/*-------------------------------------------------------------------------*/ + +/* endpoints operations */ + +/* configure endpoint, making it usable */ +static int langwell_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct langwell_udc *dev; + struct langwell_ep *ep; + u16 max = 0; + unsigned long flags; + int retval = 0; + unsigned char zlt, ios = 0, mult = 0; + + ep = container_of(_ep, struct langwell_ep, ep); + dev = ep->dev; + VDBG(dev, "---> %s()\n", __func__); + + if (!_ep || !desc || ep->desc + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + max = le16_to_cpu(desc->wMaxPacketSize); + + /* + * disable HW zero length termination select + * driver handles zero length packet through req->req.zero + */ + zlt = 1; + + /* + * sanity check type, direction, address, and then + * initialize the endpoint capabilities fields in dQH + */ + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_CONTROL: + ios = 1; + break; + case USB_ENDPOINT_XFER_BULK: + if ((dev->gadget.speed == USB_SPEED_HIGH + && max != 512) + || (dev->gadget.speed == USB_SPEED_FULL + && max > 64)) { + goto done; + } + break; + case USB_ENDPOINT_XFER_INT: + if (strstr(ep->ep.name, "-iso")) /* bulk is ok */ + goto done; + + switch (dev->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 64) + break; + default: + if (max <= 8) + break; + goto done; + } + break; + case USB_ENDPOINT_XFER_ISOC: + if (strstr(ep->ep.name, "-bulk") + || strstr(ep->ep.name, "-int")) + goto done; + + switch (dev->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 1023) + break; + default: + goto done; + } + /* + * FIXME: + * calculate transactions needed for high bandwidth iso + */ + mult = (unsigned char)(1 + ((max >> 11) & 0x03)); + max = max & 0x8ff; /* bit 0~10 */ + /* 3 transactions at most */ + if (mult > 3) + goto done; + break; + default: + goto done; + } + + spin_lock_irqsave(&dev->lock, flags); + + /* configure endpoint capabilities in dQH */ + ep->dqh->dqh_ios = ios; + ep->dqh->dqh_mpl = cpu_to_le16(max); + ep->dqh->dqh_zlt = zlt; + ep->dqh->dqh_mult = mult; + + ep->ep.maxpacket = max; + ep->desc = desc; + ep->stopped = 0; + ep->ep_num = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + + /* ep_type */ + ep->ep_type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + /* configure endpoint control registers */ + ep_reset(ep, ep->ep_num, is_in(ep), ep->ep_type); + + DBG(dev, "enabled %s (ep%d%s-%s), max %04x\n", + _ep->name, + ep->ep_num, + DIR_STRING(desc->bEndpointAddress), + type_string(desc->bmAttributes), + max); + + spin_unlock_irqrestore(&dev->lock, flags); +done: + VDBG(dev, "<--- %s()\n", __func__); + return retval; +} + + +/*-------------------------------------------------------------------------*/ + +/* retire a request */ +static void done(struct langwell_ep *ep, struct langwell_request *req, + int status) +{ + struct langwell_udc *dev = ep->dev; + unsigned stopped = ep->stopped; + struct langwell_dtd *curr_dtd, *next_dtd; + int i; + + VDBG(dev, "---> %s()\n", __func__); + + /* remove the req from ep->queue */ + list_del_init(&req->queue); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + /* free dTD for the request */ + next_dtd = req->head; + for (i = 0; i < req->dtd_count; i++) { + curr_dtd = next_dtd; + if (i != req->dtd_count - 1) + next_dtd = curr_dtd->next_dtd_virt; + dma_pool_free(dev->dtd_pool, curr_dtd, curr_dtd->dtd_dma); + } + + if (req->mapped) { + dma_unmap_single(&dev->pdev->dev, req->req.dma, req->req.length, + is_in(ep) ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } else + dma_sync_single_for_cpu(&dev->pdev->dev, req->req.dma, + req->req.length, + is_in(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + if (status != -ESHUTDOWN) + DBG(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(&dev->lock); + /* complete routine from gadget driver */ + if (req->req.complete) + req->req.complete(&ep->ep, &req->req); + + spin_lock(&dev->lock); + ep->stopped = stopped; + + VDBG(dev, "<--- %s()\n", __func__); +} + + +static void langwell_ep_fifo_flush(struct usb_ep *_ep); + +/* delete all endpoint requests, called with spinlock held */ +static void nuke(struct langwell_ep *ep, int status) +{ + /* called with spinlock held */ + ep->stopped = 1; + + /* endpoint fifo flush */ + if (&ep->ep && ep->desc) + langwell_ep_fifo_flush(&ep->ep); + + while (!list_empty(&ep->queue)) { + struct langwell_request *req = NULL; + req = list_entry(ep->queue.next, struct langwell_request, + queue); + done(ep, req, status); + } +} + + +/*-------------------------------------------------------------------------*/ + +/* endpoint is no longer usable */ +static int langwell_ep_disable(struct usb_ep *_ep) +{ + struct langwell_ep *ep; + unsigned long flags; + struct langwell_udc *dev; + int ep_num; + u32 endptctrl; + + ep = container_of(_ep, struct langwell_ep, ep); + dev = ep->dev; + VDBG(dev, "---> %s()\n", __func__); + + if (!_ep || !ep->desc) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + + /* disable endpoint control register */ + ep_num = ep->ep_num; + endptctrl = readl(&dev->op_regs->endptctrl[ep_num]); + if (is_in(ep)) + endptctrl &= ~EPCTRL_TXE; + else + endptctrl &= ~EPCTRL_RXE; + writel(endptctrl, &dev->op_regs->endptctrl[ep_num]); + + /* nuke all pending requests (does flush) */ + nuke(ep, -ESHUTDOWN); + + ep->desc = NULL; + ep->stopped = 1; + + spin_unlock_irqrestore(&dev->lock, flags); + + DBG(dev, "disabled %s\n", _ep->name); + VDBG(dev, "<--- %s()\n", __func__); + + return 0; +} + + +/* allocate a request object to use with this endpoint */ +static struct usb_request *langwell_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct langwell_ep *ep; + struct langwell_udc *dev; + struct langwell_request *req = NULL; + + if (!_ep) + return NULL; + + ep = container_of(_ep, struct langwell_ep, ep); + dev = ep->dev; + VDBG(dev, "---> %s()\n", __func__); + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + req->req.dma = DMA_ADDR_INVALID; + INIT_LIST_HEAD(&req->queue); + + VDBG(dev, "alloc request for %s\n", _ep->name); + VDBG(dev, "<--- %s()\n", __func__); + return &req->req; +} + + +/* free a request object */ +static void langwell_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct langwell_ep *ep; + struct langwell_udc *dev; + struct langwell_request *req = NULL; + + ep = container_of(_ep, struct langwell_ep, ep); + dev = ep->dev; + VDBG(dev, "---> %s()\n", __func__); + + if (!_ep || !_req) + return; + + req = container_of(_req, struct langwell_request, req); + WARN_ON(!list_empty(&req->queue)); + + if (_req) + kfree(req); + + VDBG(dev, "free request for %s\n", _ep->name); + VDBG(dev, "<--- %s()\n", __func__); +} + + +/*-------------------------------------------------------------------------*/ + +/* queue dTD and PRIME endpoint */ +static int queue_dtd(struct langwell_ep *ep, struct langwell_request *req) +{ + u32 bit_mask, usbcmd, endptstat, dtd_dma; + u8 dtd_status; + int i; + struct langwell_dqh *dqh; + struct langwell_udc *dev; + + dev = ep->dev; + VDBG(dev, "---> %s()\n", __func__); + + i = ep->ep_num * 2 + is_in(ep); + dqh = &dev->ep_dqh[i]; + + if (ep->ep_num) + VDBG(dev, "%s\n", ep->name); + else + /* ep0 */ + VDBG(dev, "%s-%s\n", ep->name, is_in(ep) ? "in" : "out"); + + VDBG(dev, "ep_dqh[%d] addr: 0x%08x\n", i, (u32)&(dev->ep_dqh[i])); + + bit_mask = is_in(ep) ? + (1 << (ep->ep_num + 16)) : (1 << (ep->ep_num)); + + VDBG(dev, "bit_mask = 0x%08x\n", bit_mask); + + /* check if the pipe is empty */ + if (!(list_empty(&ep->queue))) { + /* add dTD to the end of linked list */ + struct langwell_request *lastreq; + lastreq = list_entry(ep->queue.prev, + struct langwell_request, queue); + + lastreq->tail->dtd_next = + cpu_to_le32(req->head->dtd_dma & DTD_NEXT_MASK); + + /* read prime bit, if 1 goto out */ + if (readl(&dev->op_regs->endptprime) & bit_mask) + goto out; + + do { + /* set ATDTW bit in USBCMD */ + usbcmd = readl(&dev->op_regs->usbcmd); + writel(usbcmd | CMD_ATDTW, &dev->op_regs->usbcmd); + + /* read correct status bit */ + endptstat = readl(&dev->op_regs->endptstat) & bit_mask; + + } while (!(readl(&dev->op_regs->usbcmd) & CMD_ATDTW)); + + /* write ATDTW bit to 0 */ + usbcmd = readl(&dev->op_regs->usbcmd); + writel(usbcmd & ~CMD_ATDTW, &dev->op_regs->usbcmd); + + if (endptstat) + goto out; + } + + /* write dQH next pointer and terminate bit to 0 */ + dtd_dma = req->head->dtd_dma & DTD_NEXT_MASK; + dqh->dtd_next = cpu_to_le32(dtd_dma); + + /* clear active and halt bit */ + dtd_status = (u8) ~(DTD_STS_ACTIVE | DTD_STS_HALTED); + dqh->dtd_status &= dtd_status; + VDBG(dev, "dqh->dtd_status = 0x%x\n", dqh->dtd_status); + + /* write 1 to endptprime register to PRIME endpoint */ + bit_mask = is_in(ep) ? (1 << (ep->ep_num + 16)) : (1 << ep->ep_num); + VDBG(dev, "endprime bit_mask = 0x%08x\n", bit_mask); + writel(bit_mask, &dev->op_regs->endptprime); +out: + VDBG(dev, "<--- %s()\n", __func__); + return 0; +} + + +/* fill in the dTD structure to build a transfer descriptor */ +static struct langwell_dtd *build_dtd(struct langwell_request *req, + unsigned *length, dma_addr_t *dma, int *is_last) +{ + u32 buf_ptr; + struct langwell_dtd *dtd; + struct langwell_udc *dev; + int i; + + dev = req->ep->dev; + VDBG(dev, "---> %s()\n", __func__); + + /* the maximum transfer length, up to 16k bytes */ + *length = min(req->req.length - req->req.actual, + (unsigned)DTD_MAX_TRANSFER_LENGTH); + + /* create dTD dma_pool resource */ + dtd = dma_pool_alloc(dev->dtd_pool, GFP_KERNEL, dma); + if (dtd == NULL) + return dtd; + dtd->dtd_dma = *dma; + + /* initialize buffer page pointers */ + buf_ptr = (u32)(req->req.dma + req->req.actual); + for (i = 0; i < 5; i++) + dtd->dtd_buf[i] = cpu_to_le32(buf_ptr + i * PAGE_SIZE); + + req->req.actual += *length; + + /* fill in total bytes with transfer size */ + dtd->dtd_total = cpu_to_le16(*length); + VDBG(dev, "dtd->dtd_total = %d\n", dtd->dtd_total); + + /* set is_last flag if req->req.zero is set or not */ + if (req->req.zero) { + if (*length == 0 || (*length % req->ep->ep.maxpacket) != 0) + *is_last = 1; + else + *is_last = 0; + } else if (req->req.length == req->req.actual) { + *is_last = 1; + } else + *is_last = 0; + + if (*is_last == 0) + VDBG(dev, "multi-dtd request!\n"); + + /* set interrupt on complete bit for the last dTD */ + if (*is_last && !req->req.no_interrupt) + dtd->dtd_ioc = 1; + + /* set multiplier override 0 for non-ISO and non-TX endpoint */ + dtd->dtd_multo = 0; + + /* set the active bit of status field to 1 */ + dtd->dtd_status = DTD_STS_ACTIVE; + VDBG(dev, "dtd->dtd_status = 0x%02x\n", dtd->dtd_status); + + VDBG(dev, "length = %d, dma addr= 0x%08x\n", *length, (int)*dma); + VDBG(dev, "<--- %s()\n", __func__); + return dtd; +} + + +/* generate dTD linked list for a request */ +static int req_to_dtd(struct langwell_request *req) +{ + unsigned count; + int is_last, is_first = 1; + struct langwell_dtd *dtd, *last_dtd = NULL; + struct langwell_udc *dev; + dma_addr_t dma; + + dev = req->ep->dev; + VDBG(dev, "---> %s()\n", __func__); + do { + dtd = build_dtd(req, &count, &dma, &is_last); + if (dtd == NULL) + return -ENOMEM; + + if (is_first) { + is_first = 0; + req->head = dtd; + } else { + last_dtd->dtd_next = cpu_to_le32(dma); + last_dtd->next_dtd_virt = dtd; + } + last_dtd = dtd; + req->dtd_count++; + } while (!is_last); + + /* set terminate bit to 1 for the last dTD */ + dtd->dtd_next = DTD_TERM; + + req->tail = dtd; + + VDBG(dev, "<--- %s()\n", __func__); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* queue (submits) an I/O requests to an endpoint */ +static int langwell_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct langwell_request *req; + struct langwell_ep *ep; + struct langwell_udc *dev; + unsigned long flags; + int is_iso = 0, zlflag = 0; + + /* always require a cpu-view buffer */ + req = container_of(_req, struct langwell_request, req); + ep = container_of(_ep, struct langwell_ep, ep); + + if (!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue)) { + return -EINVAL; + } + + if (unlikely(!_ep || !ep->desc)) + return -EINVAL; + + dev = ep->dev; + req->ep = ep; + VDBG(dev, "---> %s()\n", __func__); + + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (req->req.length > ep->ep.maxpacket) + return -EMSGSIZE; + is_iso = 1; + } + + if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + /* set up dma mapping in case the caller didn't */ + if (_req->dma == DMA_ADDR_INVALID) { + /* WORKAROUND: WARN_ON(size == 0) */ + if (_req->length == 0) { + VDBG(dev, "req->length: 0->1\n"); + zlflag = 1; + _req->length++; + } + + _req->dma = dma_map_single(&dev->pdev->dev, + _req->buf, _req->length, + is_in(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + if (zlflag && (_req->length == 1)) { + VDBG(dev, "req->length: 1->0\n"); + zlflag = 0; + _req->length = 0; + } + + req->mapped = 1; + VDBG(dev, "req->mapped = 1\n"); + } else { + dma_sync_single_for_device(&dev->pdev->dev, + _req->dma, _req->length, + is_in(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + req->mapped = 0; + VDBG(dev, "req->mapped = 0\n"); + } + + DBG(dev, "%s queue req %p, len %u, buf %p, dma 0x%08x\n", + _ep->name, + _req, _req->length, _req->buf, _req->dma); + + _req->status = -EINPROGRESS; + _req->actual = 0; + req->dtd_count = 0; + + spin_lock_irqsave(&dev->lock, flags); + + /* build and put dTDs to endpoint queue */ + if (!req_to_dtd(req)) { + queue_dtd(ep, req); + } else { + spin_unlock_irqrestore(&dev->lock, flags); + return -ENOMEM; + } + + /* update ep0 state */ + if (ep->ep_num == 0) + dev->ep0_state = DATA_STATE_XMIT; + + if (likely(req != NULL)) { + list_add_tail(&req->queue, &ep->queue); + VDBG(dev, "list_add_tail() \n"); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + VDBG(dev, "<--- %s()\n", __func__); + return 0; +} + + +/* dequeue (cancels, unlinks) an I/O request from an endpoint */ +static int langwell_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct langwell_ep *ep; + struct langwell_udc *dev; + struct langwell_request *req; + unsigned long flags; + int stopped, ep_num, retval = 0; + u32 endptctrl; + + ep = container_of(_ep, struct langwell_ep, ep); + dev = ep->dev; + VDBG(dev, "---> %s()\n", __func__); + + if (!_ep || !ep->desc || !_req) + return -EINVAL; + + if (!dev->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&dev->lock, flags); + stopped = ep->stopped; + + /* quiesce dma while we patch the queue */ + ep->stopped = 1; + ep_num = ep->ep_num; + + /* disable endpoint control register */ + endptctrl = readl(&dev->op_regs->endptctrl[ep_num]); + if (is_in(ep)) + endptctrl &= ~EPCTRL_TXE; + else + endptctrl &= ~EPCTRL_RXE; + writel(endptctrl, &dev->op_regs->endptctrl[ep_num]); + + /* make sure it's still queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + + if (&req->req != _req) { + retval = -EINVAL; + goto done; + } + + /* queue head may be partially complete. */ + if (ep->queue.next == &req->queue) { + DBG(dev, "unlink (%s) dma\n", _ep->name); + _req->status = -ECONNRESET; + langwell_ep_fifo_flush(&ep->ep); + + /* not the last request in endpoint queue */ + if (likely(ep->queue.next == &req->queue)) { + struct langwell_dqh *dqh; + struct langwell_request *next_req; + + dqh = ep->dqh; + next_req = list_entry(req->queue.next, + struct langwell_request, queue); + + /* point the dQH to the first dTD of next request */ + writel((u32) next_req->head, &dqh->dqh_current); + } + } else { + struct langwell_request *prev_req; + + prev_req = list_entry(req->queue.prev, + struct langwell_request, queue); + writel(readl(&req->tail->dtd_next), + &prev_req->tail->dtd_next); + } + + done(ep, req, -ECONNRESET); + +done: + /* enable endpoint again */ + endptctrl = readl(&dev->op_regs->endptctrl[ep_num]); + if (is_in(ep)) + endptctrl |= EPCTRL_TXE; + else + endptctrl |= EPCTRL_RXE; + writel(endptctrl, &dev->op_regs->endptctrl[ep_num]); + + ep->stopped = stopped; + spin_unlock_irqrestore(&dev->lock, flags); + + VDBG(dev, "<--- %s()\n", __func__); + return retval; +} + + +/*-------------------------------------------------------------------------*/ + +/* endpoint set/clear halt */ +static void ep_set_halt(struct langwell_ep *ep, int value) +{ + u32 endptctrl = 0; + int ep_num; + struct langwell_udc *dev = ep->dev; + VDBG(dev, "---> %s()\n", __func__); + + ep_num = ep->ep_num; + endptctrl = readl(&dev->op_regs->endptctrl[ep_num]); + + /* value: 1 - set halt, 0 - clear halt */ + if (value) { + /* set the stall bit */ + if (is_in(ep)) + endptctrl |= EPCTRL_TXS; + else + endptctrl |= EPCTRL_RXS; + } else { + /* clear the stall bit and reset data toggle */ + if (is_in(ep)) { + endptctrl &= ~EPCTRL_TXS; + endptctrl |= EPCTRL_TXR; + } else { + endptctrl &= ~EPCTRL_RXS; + endptctrl |= EPCTRL_RXR; + } + } + + writel(endptctrl, &dev->op_regs->endptctrl[ep_num]); + + VDBG(dev, "<--- %s()\n", __func__); +} + + +/* set the endpoint halt feature */ +static int langwell_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct langwell_ep *ep; + struct langwell_udc *dev; + unsigned long flags; + int retval = 0; + + ep = container_of(_ep, struct langwell_ep, ep); + dev = ep->dev; + + VDBG(dev, "---> %s()\n", __func__); + + if (!_ep || !ep->desc) + return -EINVAL; + + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + if (ep->desc && (ep->desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_ISOC) + return -EOPNOTSUPP; + + spin_lock_irqsave(&dev->lock, flags); + + /* + * attempt to halt IN ep will fail if any transfer requests + * are still queue + */ + if (!list_empty(&ep->queue) && is_in(ep) && value) { + /* IN endpoint FIFO holds bytes */ + DBG(dev, "%s FIFO holds bytes\n", _ep->name); + retval = -EAGAIN; + goto done; + } + + /* endpoint set/clear halt */ + if (ep->ep_num) { + ep_set_halt(ep, value); + } else { /* endpoint 0 */ + dev->ep0_state = WAIT_FOR_SETUP; + dev->ep0_dir = USB_DIR_OUT; + } +done: + spin_unlock_irqrestore(&dev->lock, flags); + DBG(dev, "%s %s halt\n", _ep->name, value ? "set" : "clear"); + VDBG(dev, "<--- %s()\n", __func__); + return retval; +} + + +/* set the halt feature and ignores clear requests */ +static int langwell_ep_set_wedge(struct usb_ep *_ep) +{ + struct langwell_ep *ep; + struct langwell_udc *dev; + + ep = container_of(_ep, struct langwell_ep, ep); + dev = ep->dev; + + VDBG(dev, "---> %s()\n", __func__); + + if (!_ep || !ep->desc) + return -EINVAL; + + VDBG(dev, "<--- %s()\n", __func__); + return usb_ep_set_halt(_ep); +} + + +/* flush contents of a fifo */ +static void langwell_ep_fifo_flush(struct usb_ep *_ep) +{ + struct langwell_ep *ep; + struct langwell_udc *dev; + u32 flush_bit; + unsigned long timeout; + + ep = container_of(_ep, struct langwell_ep, ep); + dev = ep->dev; + + VDBG(dev, "---> %s()\n", __func__); + + if (!_ep || !ep->desc) { + VDBG(dev, "ep or ep->desc is NULL\n"); + VDBG(dev, "<--- %s()\n", __func__); + return; + } + + VDBG(dev, "%s-%s fifo flush\n", _ep->name, is_in(ep) ? "in" : "out"); + + /* flush endpoint buffer */ + if (ep->ep_num == 0) + flush_bit = (1 << 16) | 1; + else if (is_in(ep)) + flush_bit = 1 << (ep->ep_num + 16); /* TX */ + else + flush_bit = 1 << ep->ep_num; /* RX */ + + /* wait until flush complete */ + timeout = jiffies + FLUSH_TIMEOUT; + do { + writel(flush_bit, &dev->op_regs->endptflush); + while (readl(&dev->op_regs->endptflush)) { + if (time_after(jiffies, timeout)) { + ERROR(dev, "ep flush timeout\n"); + goto done; + } + cpu_relax(); + } + } while (readl(&dev->op_regs->endptstat) & flush_bit); +done: + VDBG(dev, "<--- %s()\n", __func__); +} + + +/* endpoints operations structure */ +static const struct usb_ep_ops langwell_ep_ops = { + + /* configure endpoint, making it usable */ + .enable = langwell_ep_enable, + + /* endpoint is no longer usable */ + .disable = langwell_ep_disable, + + /* allocate a request object to use with this endpoint */ + .alloc_request = langwell_alloc_request, + + /* free a request object */ + .free_request = langwell_free_request, + + /* queue (submits) an I/O requests to an endpoint */ + .queue = langwell_ep_queue, + + /* dequeue (cancels, unlinks) an I/O request from an endpoint */ + .dequeue = langwell_ep_dequeue, + + /* set the endpoint halt feature */ + .set_halt = langwell_ep_set_halt, + + /* set the halt feature and ignores clear requests */ + .set_wedge = langwell_ep_set_wedge, + + /* flush contents of a fifo */ + .fifo_flush = langwell_ep_fifo_flush, +}; + + +/*-------------------------------------------------------------------------*/ + +/* device controller usb_gadget_ops structure */ + +/* returns the current frame number */ +static int langwell_get_frame(struct usb_gadget *_gadget) +{ + struct langwell_udc *dev; + u16 retval; + + if (!_gadget) + return -ENODEV; + + dev = container_of(_gadget, struct langwell_udc, gadget); + VDBG(dev, "---> %s()\n", __func__); + + retval = readl(&dev->op_regs->frindex) & FRINDEX_MASK; + + VDBG(dev, "<--- %s()\n", __func__); + return retval; +} + + +/* tries to wake up the host connected to this gadget */ +static int langwell_wakeup(struct usb_gadget *_gadget) +{ + struct langwell_udc *dev; + u32 portsc1, devlc; + unsigned long flags; + + if (!_gadget) + return 0; + + dev = container_of(_gadget, struct langwell_udc, gadget); + VDBG(dev, "---> %s()\n", __func__); + + /* Remote Wakeup feature not enabled by host */ + if (!dev->remote_wakeup) + return -ENOTSUPP; + + spin_lock_irqsave(&dev->lock, flags); + + portsc1 = readl(&dev->op_regs->portsc1); + if (!(portsc1 & PORTS_SUSP)) { + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + } + + /* LPM L1 to L0, remote wakeup */ + if (dev->lpm && dev->lpm_state == LPM_L1) { + portsc1 |= PORTS_SLP; + writel(portsc1, &dev->op_regs->portsc1); + } + + /* force port resume */ + if (dev->usb_state == USB_STATE_SUSPENDED) { + portsc1 |= PORTS_FPR; + writel(portsc1, &dev->op_regs->portsc1); + } + + /* exit PHY low power suspend */ + devlc = readl(&dev->op_regs->devlc); + VDBG(dev, "devlc = 0x%08x\n", devlc); + devlc &= ~LPM_PHCD; + writel(devlc, &dev->op_regs->devlc); + + spin_unlock_irqrestore(&dev->lock, flags); + + VDBG(dev, "<--- %s()\n", __func__); + return 0; +} + + +/* notify controller that VBUS is powered or not */ +static int langwell_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct langwell_udc *dev; + unsigned long flags; + u32 usbcmd; + + if (!_gadget) + return -ENODEV; + + dev = container_of(_gadget, struct langwell_udc, gadget); + VDBG(dev, "---> %s()\n", __func__); + + spin_lock_irqsave(&dev->lock, flags); + VDBG(dev, "VBUS status: %s\n", is_active ? "on" : "off"); + + dev->vbus_active = (is_active != 0); + if (dev->driver && dev->softconnected && dev->vbus_active) { + usbcmd = readl(&dev->op_regs->usbcmd); + usbcmd |= CMD_RUNSTOP; + writel(usbcmd, &dev->op_regs->usbcmd); + } else { + usbcmd = readl(&dev->op_regs->usbcmd); + usbcmd &= ~CMD_RUNSTOP; + writel(usbcmd, &dev->op_regs->usbcmd); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + VDBG(dev, "<--- %s()\n", __func__); + return 0; +} + + +/* constrain controller's VBUS power usage */ +static int langwell_vbus_draw(struct usb_gadget *_gadget, unsigned mA) +{ + struct langwell_udc *dev; + + if (!_gadget) + return -ENODEV; + + dev = container_of(_gadget, struct langwell_udc, gadget); + VDBG(dev, "---> %s()\n", __func__); + + if (dev->transceiver) { + VDBG(dev, "otg_set_power\n"); + VDBG(dev, "<--- %s()\n", __func__); + return otg_set_power(dev->transceiver, mA); + } + + VDBG(dev, "<--- %s()\n", __func__); + return -ENOTSUPP; +} + + +/* D+ pullup, software-controlled connect/disconnect to USB host */ +static int langwell_pullup(struct usb_gadget *_gadget, int is_on) +{ + struct langwell_udc *dev; + u32 usbcmd; + unsigned long flags; + + if (!_gadget) + return -ENODEV; + + dev = container_of(_gadget, struct langwell_udc, gadget); + + VDBG(dev, "---> %s()\n", __func__); + + spin_lock_irqsave(&dev->lock, flags); + dev->softconnected = (is_on != 0); + + if (dev->driver && dev->softconnected && dev->vbus_active) { + usbcmd = readl(&dev->op_regs->usbcmd); + usbcmd |= CMD_RUNSTOP; + writel(usbcmd, &dev->op_regs->usbcmd); + } else { + usbcmd = readl(&dev->op_regs->usbcmd); + usbcmd &= ~CMD_RUNSTOP; + writel(usbcmd, &dev->op_regs->usbcmd); + } + spin_unlock_irqrestore(&dev->lock, flags); + + VDBG(dev, "<--- %s()\n", __func__); + return 0; +} + + +/* device controller usb_gadget_ops structure */ +static const struct usb_gadget_ops langwell_ops = { + + /* returns the current frame number */ + .get_frame = langwell_get_frame, + + /* tries to wake up the host connected to this gadget */ + .wakeup = langwell_wakeup, + + /* set the device selfpowered feature, always selfpowered */ + /* .set_selfpowered = langwell_set_selfpowered, */ + + /* notify controller that VBUS is powered or not */ + .vbus_session = langwell_vbus_session, + + /* constrain controller's VBUS power usage */ + .vbus_draw = langwell_vbus_draw, + + /* D+ pullup, software-controlled connect/disconnect to USB host */ + .pullup = langwell_pullup, +}; + + +/*-------------------------------------------------------------------------*/ + +/* device controller operations */ + +/* reset device controller */ +static int langwell_udc_reset(struct langwell_udc *dev) +{ + u32 usbcmd, usbmode, devlc, endpointlistaddr; + unsigned long timeout; + + if (!dev) + return -EINVAL; + + DBG(dev, "---> %s()\n", __func__); + + /* set controller to stop state */ + usbcmd = readl(&dev->op_regs->usbcmd); + usbcmd &= ~CMD_RUNSTOP; + writel(usbcmd, &dev->op_regs->usbcmd); + + /* reset device controller */ + usbcmd = readl(&dev->op_regs->usbcmd); + usbcmd |= CMD_RST; + writel(usbcmd, &dev->op_regs->usbcmd); + + /* wait for reset to complete */ + timeout = jiffies + RESET_TIMEOUT; + while (readl(&dev->op_regs->usbcmd) & CMD_RST) { + if (time_after(jiffies, timeout)) { + ERROR(dev, "device reset timeout\n"); + return -ETIMEDOUT; + } + cpu_relax(); + } + + /* set controller to device mode */ + usbmode = readl(&dev->op_regs->usbmode); + usbmode |= MODE_DEVICE; + + /* turn setup lockout off, require setup tripwire in usbcmd */ + usbmode |= MODE_SLOM; + + writel(usbmode, &dev->op_regs->usbmode); + usbmode = readl(&dev->op_regs->usbmode); + VDBG(dev, "usbmode=0x%08x\n", usbmode); + + /* Write-Clear setup status */ + writel(0, &dev->op_regs->usbsts); + + /* if support USB LPM, ACK all LPM token */ + if (dev->lpm) { + devlc = readl(&dev->op_regs->devlc); + devlc &= ~LPM_STL; /* don't STALL LPM token */ + devlc &= ~LPM_NYT_ACK; /* ACK LPM token */ + writel(devlc, &dev->op_regs->devlc); + } + + /* fill endpointlistaddr register */ + endpointlistaddr = dev->ep_dqh_dma; + endpointlistaddr &= ENDPOINTLISTADDR_MASK; + writel(endpointlistaddr, &dev->op_regs->endpointlistaddr); + + VDBG(dev, "dQH base (vir: %p, phy: 0x%08x), endpointlistaddr=0x%08x\n", + dev->ep_dqh, endpointlistaddr, + readl(&dev->op_regs->endpointlistaddr)); + DBG(dev, "<--- %s()\n", __func__); + return 0; +} + + +/* reinitialize device controller endpoints */ +static int eps_reinit(struct langwell_udc *dev) +{ + struct langwell_ep *ep; + char name[14]; + int i; + + VDBG(dev, "---> %s()\n", __func__); + + /* initialize ep0 */ + ep = &dev->ep[0]; + ep->dev = dev; + strncpy(ep->name, "ep0", sizeof(ep->name)); + ep->ep.name = ep->name; + ep->ep.ops = &langwell_ep_ops; + ep->stopped = 0; + ep->ep.maxpacket = EP0_MAX_PKT_SIZE; + ep->ep_num = 0; + ep->desc = &langwell_ep0_desc; + INIT_LIST_HEAD(&ep->queue); + + ep->ep_type = USB_ENDPOINT_XFER_CONTROL; + + /* initialize other endpoints */ + for (i = 2; i < dev->ep_max; i++) { + ep = &dev->ep[i]; + if (i % 2) + snprintf(name, sizeof(name), "ep%din", i / 2); + else + snprintf(name, sizeof(name), "ep%dout", i / 2); + ep->dev = dev; + strncpy(ep->name, name, sizeof(ep->name)); + ep->ep.name = ep->name; + + ep->ep.ops = &langwell_ep_ops; + ep->stopped = 0; + ep->ep.maxpacket = (unsigned short) ~0; + ep->ep_num = i / 2; + + INIT_LIST_HEAD(&ep->queue); + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->dqh = &dev->ep_dqh[i]; + } + + VDBG(dev, "<--- %s()\n", __func__); + return 0; +} + + +/* enable interrupt and set controller to run state */ +static void langwell_udc_start(struct langwell_udc *dev) +{ + u32 usbintr, usbcmd; + DBG(dev, "---> %s()\n", __func__); + + /* enable interrupts */ + usbintr = INTR_ULPIE /* ULPI */ + | INTR_SLE /* suspend */ + /* | INTR_SRE SOF received */ + | INTR_URE /* USB reset */ + | INTR_AAE /* async advance */ + | INTR_SEE /* system error */ + | INTR_FRE /* frame list rollover */ + | INTR_PCE /* port change detect */ + | INTR_UEE /* USB error interrupt */ + | INTR_UE; /* USB interrupt */ + writel(usbintr, &dev->op_regs->usbintr); + + /* clear stopped bit */ + dev->stopped = 0; + + /* set controller to run */ + usbcmd = readl(&dev->op_regs->usbcmd); + usbcmd |= CMD_RUNSTOP; + writel(usbcmd, &dev->op_regs->usbcmd); + + DBG(dev, "<--- %s()\n", __func__); + return; +} + + +/* disable interrupt and set controller to stop state */ +static void langwell_udc_stop(struct langwell_udc *dev) +{ + u32 usbcmd; + + DBG(dev, "---> %s()\n", __func__); + + /* disable all interrupts */ + writel(0, &dev->op_regs->usbintr); + + /* set stopped bit */ + dev->stopped = 1; + + /* set controller to stop state */ + usbcmd = readl(&dev->op_regs->usbcmd); + usbcmd &= ~CMD_RUNSTOP; + writel(usbcmd, &dev->op_regs->usbcmd); + + DBG(dev, "<--- %s()\n", __func__); + return; +} + + +/* stop all USB activities */ +static void stop_activity(struct langwell_udc *dev, + struct usb_gadget_driver *driver) +{ + struct langwell_ep *ep; + DBG(dev, "---> %s()\n", __func__); + + nuke(&dev->ep[0], -ESHUTDOWN); + + list_for_each_entry(ep, &dev->gadget.ep_list, ep.ep_list) { + nuke(ep, -ESHUTDOWN); + } + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + DBG(dev, "<--- %s()\n", __func__); +} + + +/*-------------------------------------------------------------------------*/ + +/* device "function" sysfs attribute file */ +static ssize_t show_function(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct langwell_udc *dev = the_controller; + + if (!dev->driver || !dev->driver->function + || strlen(dev->driver->function) > PAGE_SIZE) + return 0; + + return scnprintf(buf, PAGE_SIZE, "%s\n", dev->driver->function); +} +static DEVICE_ATTR(function, S_IRUGO, show_function, NULL); + + +/* device "langwell_udc" sysfs attribute file */ +static ssize_t show_langwell_udc(struct device *_dev, + struct device_attribute *attr, char *buf) +{ + struct langwell_udc *dev = the_controller; + struct langwell_request *req; + struct langwell_ep *ep = NULL; + char *next; + unsigned size; + unsigned t; + unsigned i; + unsigned long flags; + u32 tmp_reg; + + next = buf; + size = PAGE_SIZE; + spin_lock_irqsave(&dev->lock, flags); + + /* driver basic information */ + t = scnprintf(next, size, + DRIVER_DESC "\n" + "%s version: %s\n" + "Gadget driver: %s\n\n", + driver_name, DRIVER_VERSION, + dev->driver ? dev->driver->driver.name : "(none)"); + size -= t; + next += t; + + /* device registers */ + tmp_reg = readl(&dev->op_regs->usbcmd); + t = scnprintf(next, size, + "USBCMD reg:\n" + "SetupTW: %d\n" + "Run/Stop: %s\n\n", + (tmp_reg & CMD_SUTW) ? 1 : 0, + (tmp_reg & CMD_RUNSTOP) ? "Run" : "Stop"); + size -= t; + next += t; + + tmp_reg = readl(&dev->op_regs->usbsts); + t = scnprintf(next, size, + "USB Status Reg:\n" + "Device Suspend: %d\n" + "Reset Received: %d\n" + "System Error: %s\n" + "USB Error Interrupt: %s\n\n", + (tmp_reg & STS_SLI) ? 1 : 0, + (tmp_reg & STS_URI) ? 1 : 0, + (tmp_reg & STS_SEI) ? "Error" : "No error", + (tmp_reg & STS_UEI) ? "Error detected" : "No error"); + size -= t; + next += t; + + tmp_reg = readl(&dev->op_regs->usbintr); + t = scnprintf(next, size, + "USB Intrrupt Enable Reg:\n" + "Sleep Enable: %d\n" + "SOF Received Enable: %d\n" + "Reset Enable: %d\n" + "System Error Enable: %d\n" + "Port Change Dectected Enable: %d\n" + "USB Error Intr Enable: %d\n" + "USB Intr Enable: %d\n\n", + (tmp_reg & INTR_SLE) ? 1 : 0, + (tmp_reg & INTR_SRE) ? 1 : 0, + (tmp_reg & INTR_URE) ? 1 : 0, + (tmp_reg & INTR_SEE) ? 1 : 0, + (tmp_reg & INTR_PCE) ? 1 : 0, + (tmp_reg & INTR_UEE) ? 1 : 0, + (tmp_reg & INTR_UE) ? 1 : 0); + size -= t; + next += t; + + tmp_reg = readl(&dev->op_regs->frindex); + t = scnprintf(next, size, + "USB Frame Index Reg:\n" + "Frame Number is 0x%08x\n\n", + (tmp_reg & FRINDEX_MASK)); + size -= t; + next += t; + + tmp_reg = readl(&dev->op_regs->deviceaddr); + t = scnprintf(next, size, + "USB Device Address Reg:\n" + "Device Addr is 0x%x\n\n", + USBADR(tmp_reg)); + size -= t; + next += t; + + tmp_reg = readl(&dev->op_regs->endpointlistaddr); + t = scnprintf(next, size, + "USB Endpoint List Address Reg:\n" + "Endpoint List Pointer is 0x%x\n\n", + EPBASE(tmp_reg)); + size -= t; + next += t; + + tmp_reg = readl(&dev->op_regs->portsc1); + t = scnprintf(next, size, + "USB Port Status & Control Reg:\n" + "Port Reset: %s\n" + "Port Suspend Mode: %s\n" + "Over-current Change: %s\n" + "Port Enable/Disable Change: %s\n" + "Port Enabled/Disabled: %s\n" + "Current Connect Status: %s\n\n", + (tmp_reg & PORTS_PR) ? "Reset" : "Not Reset", + (tmp_reg & PORTS_SUSP) ? "Suspend " : "Not Suspend", + (tmp_reg & PORTS_OCC) ? "Detected" : "No", + (tmp_reg & PORTS_PEC) ? "Changed" : "Not Changed", + (tmp_reg & PORTS_PE) ? "Enable" : "Not Correct", + (tmp_reg & PORTS_CCS) ? "Attached" : "Not Attached"); + size -= t; + next += t; + + tmp_reg = readl(&dev->op_regs->devlc); + t = scnprintf(next, size, + "Device LPM Control Reg:\n" + "Parallel Transceiver : %d\n" + "Serial Transceiver : %d\n" + "Port Speed: %s\n" + "Port Force Full Speed Connenct: %s\n" + "PHY Low Power Suspend Clock Disable: %s\n" + "BmAttributes: %d\n\n", + LPM_PTS(tmp_reg), + (tmp_reg & LPM_STS) ? 1 : 0, + ({ + char *s; + switch (LPM_PSPD(tmp_reg)) { + case LPM_SPEED_FULL: + s = "Full Speed"; break; + case LPM_SPEED_LOW: + s = "Low Speed"; break; + case LPM_SPEED_HIGH: + s = "High Speed"; break; + default: + s = "Unknown Speed"; break; + } + s; + }), + (tmp_reg & LPM_PFSC) ? "Force Full Speed" : "Not Force", + (tmp_reg & LPM_PHCD) ? "Disabled" : "Enabled", + LPM_BA(tmp_reg)); + size -= t; + next += t; + + tmp_reg = readl(&dev->op_regs->usbmode); + t = scnprintf(next, size, + "USB Mode Reg:\n" + "Controller Mode is : %s\n\n", ({ + char *s; + switch (MODE_CM(tmp_reg)) { + case MODE_IDLE: + s = "Idle"; break; + case MODE_DEVICE: + s = "Device Controller"; break; + case MODE_HOST: + s = "Host Controller"; break; + default: + s = "None"; break; + } + s; + })); + size -= t; + next += t; + + tmp_reg = readl(&dev->op_regs->endptsetupstat); + t = scnprintf(next, size, + "Endpoint Setup Status Reg:\n" + "SETUP on ep 0x%04x\n\n", + tmp_reg & SETUPSTAT_MASK); + size -= t; + next += t; + + for (i = 0; i < dev->ep_max / 2; i++) { + tmp_reg = readl(&dev->op_regs->endptctrl[i]); + t = scnprintf(next, size, "EP Ctrl Reg [%d]: 0x%08x\n", + i, tmp_reg); + size -= t; + next += t; + } + tmp_reg = readl(&dev->op_regs->endptprime); + t = scnprintf(next, size, "EP Prime Reg: 0x%08x\n\n", tmp_reg); + size -= t; + next += t; + + /* langwell_udc, langwell_ep, langwell_request structure information */ + ep = &dev->ep[0]; + t = scnprintf(next, size, "%s MaxPacketSize: 0x%x, ep_num: %d\n", + ep->ep.name, ep->ep.maxpacket, ep->ep_num); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length 0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + size -= t; + next += t; + } + } + /* other gadget->eplist ep */ + list_for_each_entry(ep, &dev->gadget.ep_list, ep.ep_list) { + if (ep->desc) { + t = scnprintf(next, size, + "\n%s MaxPacketSize: 0x%x, " + "ep_num: %d\n", + ep->ep.name, ep->ep.maxpacket, + ep->ep_num); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, + "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length " + "0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + size -= t; + next += t; + } + } + } + } + + spin_unlock_irqrestore(&dev->lock, flags); + return PAGE_SIZE - size; +} +static DEVICE_ATTR(langwell_udc, S_IRUGO, show_langwell_udc, NULL); + + +/*-------------------------------------------------------------------------*/ + +/* + * when a driver is successfully registered, it will receive + * control requests including set_configuration(), which enables + * non-control requests. then usb traffic follows until a + * disconnect is reported. then a host may connect again, or + * the driver might get unbound. + */ + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct langwell_udc *dev = the_controller; + unsigned long flags; + int retval; + + if (!dev) + return -ENODEV; + + DBG(dev, "---> %s()\n", __func__); + + if (dev->driver) + return -EBUSY; + + spin_lock_irqsave(&dev->lock, flags); + + /* hook up the driver ... */ + driver->driver.bus = NULL; + dev->driver = driver; + dev->gadget.dev.driver = &driver->driver; + + spin_unlock_irqrestore(&dev->lock, flags); + + retval = driver->bind(&dev->gadget); + if (retval) { + DBG(dev, "bind to driver %s --> %d\n", + driver->driver.name, retval); + dev->driver = NULL; + dev->gadget.dev.driver = NULL; + return retval; + } + + retval = device_create_file(&dev->pdev->dev, &dev_attr_function); + if (retval) + goto err_unbind; + + dev->usb_state = USB_STATE_ATTACHED; + dev->ep0_state = WAIT_FOR_SETUP; + dev->ep0_dir = USB_DIR_OUT; + + /* enable interrupt and set controller to run state */ + if (dev->got_irq) + langwell_udc_start(dev); + + VDBG(dev, "After langwell_udc_start(), print all registers:\n"); +#ifdef VERBOSE + print_all_registers(dev); +#endif + + INFO(dev, "register driver: %s\n", driver->driver.name); + VDBG(dev, "<--- %s()\n", __func__); + return 0; + +err_unbind: + driver->unbind(&dev->gadget); + dev->gadget.dev.driver = NULL; + dev->driver = NULL; + + DBG(dev, "<--- %s()\n", __func__); + return retval; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + + +/* unregister gadget driver */ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct langwell_udc *dev = the_controller; + unsigned long flags; + + if (!dev) + return -ENODEV; + + DBG(dev, "---> %s()\n", __func__); + + if (unlikely(!driver || !driver->bind || !driver->unbind)) + return -EINVAL; + + /* unbind OTG transceiver */ + if (dev->transceiver) + (void)otg_set_peripheral(dev->transceiver, 0); + + /* disable interrupt and set controller to stop state */ + langwell_udc_stop(dev); + + dev->usb_state = USB_STATE_ATTACHED; + dev->ep0_state = WAIT_FOR_SETUP; + dev->ep0_dir = USB_DIR_OUT; + + spin_lock_irqsave(&dev->lock, flags); + + /* stop all usb activities */ + dev->gadget.speed = USB_SPEED_UNKNOWN; + stop_activity(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + /* unbind gadget driver */ + driver->unbind(&dev->gadget); + dev->gadget.dev.driver = NULL; + dev->driver = NULL; + + device_remove_file(&dev->pdev->dev, &dev_attr_function); + + INFO(dev, "unregistered driver '%s'\n", driver->driver.name); + DBG(dev, "<--- %s()\n", __func__); + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + + +/*-------------------------------------------------------------------------*/ + +/* + * setup tripwire is used as a semaphore to ensure that the setup data + * payload is extracted from a dQH without being corrupted + */ +static void setup_tripwire(struct langwell_udc *dev) +{ + u32 usbcmd, + endptsetupstat; + unsigned long timeout; + struct langwell_dqh *dqh; + + VDBG(dev, "---> %s()\n", __func__); + + /* ep0 OUT dQH */ + dqh = &dev->ep_dqh[EP_DIR_OUT]; + + /* Write-Clear endptsetupstat */ + endptsetupstat = readl(&dev->op_regs->endptsetupstat); + writel(endptsetupstat, &dev->op_regs->endptsetupstat); + + /* wait until endptsetupstat is cleared */ + timeout = jiffies + SETUPSTAT_TIMEOUT; + while (readl(&dev->op_regs->endptsetupstat)) { + if (time_after(jiffies, timeout)) { + ERROR(dev, "setup_tripwire timeout\n"); + break; + } + cpu_relax(); + } + + /* while a hazard exists when setup packet arrives */ + do { + /* set setup tripwire bit */ + usbcmd = readl(&dev->op_regs->usbcmd); + writel(usbcmd | CMD_SUTW, &dev->op_regs->usbcmd); + + /* copy the setup packet to local buffer */ + memcpy(&dev->local_setup_buff, &dqh->dqh_setup, 8); + } while (!(readl(&dev->op_regs->usbcmd) & CMD_SUTW)); + + /* Write-Clear setup tripwire bit */ + usbcmd = readl(&dev->op_regs->usbcmd); + writel(usbcmd & ~CMD_SUTW, &dev->op_regs->usbcmd); + + VDBG(dev, "<--- %s()\n", __func__); +} + + +/* protocol ep0 stall, will automatically be cleared on new transaction */ +static void ep0_stall(struct langwell_udc *dev) +{ + u32 endptctrl; + + VDBG(dev, "---> %s()\n", __func__); + + /* set TX and RX to stall */ + endptctrl = readl(&dev->op_regs->endptctrl[0]); + endptctrl |= EPCTRL_TXS | EPCTRL_RXS; + writel(endptctrl, &dev->op_regs->endptctrl[0]); + + /* update ep0 state */ + dev->ep0_state = WAIT_FOR_SETUP; + dev->ep0_dir = USB_DIR_OUT; + + VDBG(dev, "<--- %s()\n", __func__); +} + + +/* PRIME a status phase for ep0 */ +static int prime_status_phase(struct langwell_udc *dev, int dir) +{ + struct langwell_request *req; + struct langwell_ep *ep; + int status = 0; + + VDBG(dev, "---> %s()\n", __func__); + + if (dir == EP_DIR_IN) + dev->ep0_dir = USB_DIR_IN; + else + dev->ep0_dir = USB_DIR_OUT; + + ep = &dev->ep[0]; + dev->ep0_state = WAIT_FOR_OUT_STATUS; + + req = dev->status_req; + + req->ep = ep; + req->req.length = 0; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->req.complete = NULL; + req->dtd_count = 0; + + if (!req_to_dtd(req)) + status = queue_dtd(ep, req); + else + return -ENOMEM; + + if (status) + ERROR(dev, "can't queue ep0 status request\n"); + + list_add_tail(&req->queue, &ep->queue); + + VDBG(dev, "<--- %s()\n", __func__); + return status; +} + + +/* SET_ADDRESS request routine */ +static void set_address(struct langwell_udc *dev, u16 value, + u16 index, u16 length) +{ + VDBG(dev, "---> %s()\n", __func__); + + /* save the new address to device struct */ + dev->dev_addr = (u8) value; + VDBG(dev, "dev->dev_addr = %d\n", dev->dev_addr); + + /* update usb state */ + dev->usb_state = USB_STATE_ADDRESS; + + /* STATUS phase */ + if (prime_status_phase(dev, EP_DIR_IN)) + ep0_stall(dev); + + VDBG(dev, "<--- %s()\n", __func__); +} + + +/* return endpoint by windex */ +static struct langwell_ep *get_ep_by_windex(struct langwell_udc *dev, + u16 wIndex) +{ + struct langwell_ep *ep; + VDBG(dev, "---> %s()\n", __func__); + + if ((wIndex & USB_ENDPOINT_NUMBER_MASK) == 0) + return &dev->ep[0]; + + list_for_each_entry(ep, &dev->gadget.ep_list, ep.ep_list) { + u8 bEndpointAddress; + if (!ep->desc) + continue; + + bEndpointAddress = ep->desc->bEndpointAddress; + if ((wIndex ^ bEndpointAddress) & USB_DIR_IN) + continue; + + if ((wIndex & USB_ENDPOINT_NUMBER_MASK) + == (bEndpointAddress & USB_ENDPOINT_NUMBER_MASK)) + return ep; + } + + VDBG(dev, "<--- %s()\n", __func__); + return NULL; +} + + +/* return whether endpoint is stalled, 0: not stalled; 1: stalled */ +static int ep_is_stall(struct langwell_ep *ep) +{ + struct langwell_udc *dev = ep->dev; + u32 endptctrl; + int retval; + + VDBG(dev, "---> %s()\n", __func__); + + endptctrl = readl(&dev->op_regs->endptctrl[ep->ep_num]); + if (is_in(ep)) + retval = endptctrl & EPCTRL_TXS ? 1 : 0; + else + retval = endptctrl & EPCTRL_RXS ? 1 : 0; + + VDBG(dev, "<--- %s()\n", __func__); + return retval; +} + + +/* GET_STATUS request routine */ +static void get_status(struct langwell_udc *dev, u8 request_type, u16 value, + u16 index, u16 length) +{ + struct langwell_request *req; + struct langwell_ep *ep; + u16 status_data = 0; /* 16 bits cpu view status data */ + int status = 0; + + VDBG(dev, "---> %s()\n", __func__); + + ep = &dev->ep[0]; + + if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + /* get device status */ + status_data = 1 << USB_DEVICE_SELF_POWERED; + status_data |= dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) { + /* get interface status */ + status_data = 0; + } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_ENDPOINT) { + /* get endpoint status */ + struct langwell_ep *epn; + epn = get_ep_by_windex(dev, index); + /* stall if endpoint doesn't exist */ + if (!epn) + goto stall; + + status_data = ep_is_stall(epn) << USB_ENDPOINT_HALT; + } + + dev->ep0_dir = USB_DIR_IN; + + /* borrow the per device status_req */ + req = dev->status_req; + + /* fill in the reqest structure */ + *((u16 *) req->req.buf) = cpu_to_le16(status_data); + req->ep = ep; + req->req.length = 2; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->req.complete = NULL; + req->dtd_count = 0; + + /* prime the data phase */ + if (!req_to_dtd(req)) + status = queue_dtd(ep, req); + else /* no mem */ + goto stall; + + if (status) { + ERROR(dev, "response error on GET_STATUS request\n"); + goto stall; + } + + list_add_tail(&req->queue, &ep->queue); + dev->ep0_state = DATA_STATE_XMIT; + + VDBG(dev, "<--- %s()\n", __func__); + return; +stall: + ep0_stall(dev); + VDBG(dev, "<--- %s()\n", __func__); +} + + +/* setup packet interrupt handler */ +static void handle_setup_packet(struct langwell_udc *dev, + struct usb_ctrlrequest *setup) +{ + u16 wValue = le16_to_cpu(setup->wValue); + u16 wIndex = le16_to_cpu(setup->wIndex); + u16 wLength = le16_to_cpu(setup->wLength); + + VDBG(dev, "---> %s()\n", __func__); + + /* ep0 fifo flush */ + nuke(&dev->ep[0], -ESHUTDOWN); + + DBG(dev, "SETUP %02x.%02x v%04x i%04x l%04x\n", + setup->bRequestType, setup->bRequest, + wValue, wIndex, wLength); + + /* RNDIS gadget delegate */ + if ((setup->bRequestType == 0x21) && (setup->bRequest == 0x00)) { + /* USB_CDC_SEND_ENCAPSULATED_COMMAND */ + goto delegate; + } + + /* USB_CDC_GET_ENCAPSULATED_RESPONSE */ + if ((setup->bRequestType == 0xa1) && (setup->bRequest == 0x01)) { + /* USB_CDC_GET_ENCAPSULATED_RESPONSE */ + goto delegate; + } + + /* We process some stardard setup requests here */ + switch (setup->bRequest) { + case USB_REQ_GET_STATUS: + DBG(dev, "SETUP: USB_REQ_GET_STATUS\n"); + /* get status, DATA and STATUS phase */ + if ((setup->bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) + != (USB_DIR_IN | USB_TYPE_STANDARD)) + break; + get_status(dev, setup->bRequestType, wValue, wIndex, wLength); + goto end; + + case USB_REQ_SET_ADDRESS: + DBG(dev, "SETUP: USB_REQ_SET_ADDRESS\n"); + /* STATUS phase */ + if (setup->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD + | USB_RECIP_DEVICE)) + break; + set_address(dev, wValue, wIndex, wLength); + goto end; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* STATUS phase */ + { + int rc = -EOPNOTSUPP; + if (setup->bRequest == USB_REQ_SET_FEATURE) + DBG(dev, "SETUP: USB_REQ_SET_FEATURE\n"); + else if (setup->bRequest == USB_REQ_CLEAR_FEATURE) + DBG(dev, "SETUP: USB_REQ_CLEAR_FEATURE\n"); + + if ((setup->bRequestType & (USB_RECIP_MASK | USB_TYPE_MASK)) + == (USB_RECIP_ENDPOINT | USB_TYPE_STANDARD)) { + struct langwell_ep *epn; + epn = get_ep_by_windex(dev, wIndex); + /* stall if endpoint doesn't exist */ + if (!epn) { + ep0_stall(dev); + goto end; + } + + if (wValue != 0 || wLength != 0 + || epn->ep_num > dev->ep_max) + break; + + spin_unlock(&dev->lock); + rc = langwell_ep_set_halt(&epn->ep, + (setup->bRequest == USB_REQ_SET_FEATURE) + ? 1 : 0); + spin_lock(&dev->lock); + + } else if ((setup->bRequestType & (USB_RECIP_MASK + | USB_TYPE_MASK)) == (USB_RECIP_DEVICE + | USB_TYPE_STANDARD)) { + if (!gadget_is_otg(&dev->gadget)) + break; + else if (setup->bRequest == USB_DEVICE_B_HNP_ENABLE) { + dev->gadget.b_hnp_enable = 1; +#ifdef OTG_TRANSCEIVER + if (!dev->lotg->otg.default_a) + dev->lotg->hsm.b_hnp_enable = 1; +#endif + } else if (setup->bRequest == USB_DEVICE_A_HNP_SUPPORT) + dev->gadget.a_hnp_support = 1; + else if (setup->bRequest == + USB_DEVICE_A_ALT_HNP_SUPPORT) + dev->gadget.a_alt_hnp_support = 1; + else + break; + rc = 0; + } else + break; + + if (rc == 0) { + if (prime_status_phase(dev, EP_DIR_IN)) + ep0_stall(dev); + } + goto end; + } + + case USB_REQ_GET_DESCRIPTOR: + DBG(dev, "SETUP: USB_REQ_GET_DESCRIPTOR\n"); + goto delegate; + + case USB_REQ_SET_DESCRIPTOR: + DBG(dev, "SETUP: USB_REQ_SET_DESCRIPTOR unsupported\n"); + goto delegate; + + case USB_REQ_GET_CONFIGURATION: + DBG(dev, "SETUP: USB_REQ_GET_CONFIGURATION\n"); + goto delegate; + + case USB_REQ_SET_CONFIGURATION: + DBG(dev, "SETUP: USB_REQ_SET_CONFIGURATION\n"); + goto delegate; + + case USB_REQ_GET_INTERFACE: + DBG(dev, "SETUP: USB_REQ_GET_INTERFACE\n"); + goto delegate; + + case USB_REQ_SET_INTERFACE: + DBG(dev, "SETUP: USB_REQ_SET_INTERFACE\n"); + goto delegate; + + case USB_REQ_SYNCH_FRAME: + DBG(dev, "SETUP: USB_REQ_SYNCH_FRAME unsupported\n"); + goto delegate; + + default: + /* delegate USB standard requests to the gadget driver */ + goto delegate; +delegate: + /* USB requests handled by gadget */ + if (wLength) { + /* DATA phase from gadget, STATUS phase from udc */ + dev->ep0_dir = (setup->bRequestType & USB_DIR_IN) + ? USB_DIR_IN : USB_DIR_OUT; + VDBG(dev, "dev->ep0_dir = 0x%x, wLength = %d\n", + dev->ep0_dir, wLength); + spin_unlock(&dev->lock); + if (dev->driver->setup(&dev->gadget, + &dev->local_setup_buff) < 0) + ep0_stall(dev); + spin_lock(&dev->lock); + dev->ep0_state = (setup->bRequestType & USB_DIR_IN) + ? DATA_STATE_XMIT : DATA_STATE_RECV; + } else { + /* no DATA phase, IN STATUS phase from gadget */ + dev->ep0_dir = USB_DIR_IN; + VDBG(dev, "dev->ep0_dir = 0x%x, wLength = %d\n", + dev->ep0_dir, wLength); + spin_unlock(&dev->lock); + if (dev->driver->setup(&dev->gadget, + &dev->local_setup_buff) < 0) + ep0_stall(dev); + spin_lock(&dev->lock); + dev->ep0_state = WAIT_FOR_OUT_STATUS; + } + break; + } +end: + VDBG(dev, "<--- %s()\n", __func__); + return; +} + + +/* transfer completion, process endpoint request and free the completed dTDs + * for this request + */ +static int process_ep_req(struct langwell_udc *dev, int index, + struct langwell_request *curr_req) +{ + struct langwell_dtd *curr_dtd; + struct langwell_dqh *curr_dqh; + int td_complete, actual, remaining_length; + int i, dir; + u8 dtd_status = 0; + int retval = 0; + + curr_dqh = &dev->ep_dqh[index]; + dir = index % 2; + + curr_dtd = curr_req->head; + td_complete = 0; + actual = curr_req->req.length; + + VDBG(dev, "---> %s()\n", __func__); + + for (i = 0; i < curr_req->dtd_count; i++) { + remaining_length = le16_to_cpu(curr_dtd->dtd_total); + actual -= remaining_length; + + /* command execution states by dTD */ + dtd_status = curr_dtd->dtd_status; + + if (!dtd_status) { + /* transfers completed successfully */ + if (!remaining_length) { + td_complete++; + VDBG(dev, "dTD transmitted successfully\n"); + } else { + if (dir) { + VDBG(dev, "TX dTD remains data\n"); + retval = -EPROTO; + break; + + } else { + td_complete++; + break; + } + } + } else { + /* transfers completed with errors */ + if (dtd_status & DTD_STS_ACTIVE) { + DBG(dev, "request not completed\n"); + retval = 1; + return retval; + } else if (dtd_status & DTD_STS_HALTED) { + ERROR(dev, "dTD error %08x dQH[%d]\n", + dtd_status, index); + /* clear the errors and halt condition */ + curr_dqh->dtd_status = 0; + retval = -EPIPE; + break; + } else if (dtd_status & DTD_STS_DBE) { + DBG(dev, "data buffer (overflow) error\n"); + retval = -EPROTO; + break; + } else if (dtd_status & DTD_STS_TRE) { + DBG(dev, "transaction(ISO) error\n"); + retval = -EILSEQ; + break; + } else + ERROR(dev, "unknown error (0x%x)!\n", + dtd_status); + } + + if (i != curr_req->dtd_count - 1) + curr_dtd = (struct langwell_dtd *) + curr_dtd->next_dtd_virt; + } + + if (retval) + return retval; + + curr_req->req.actual = actual; + + VDBG(dev, "<--- %s()\n", __func__); + return 0; +} + + +/* complete DATA or STATUS phase of ep0 prime status phase if needed */ +static void ep0_req_complete(struct langwell_udc *dev, + struct langwell_ep *ep0, struct langwell_request *req) +{ + u32 new_addr; + VDBG(dev, "---> %s()\n", __func__); + + if (dev->usb_state == USB_STATE_ADDRESS) { + /* set the new address */ + new_addr = (u32)dev->dev_addr; + writel(new_addr << USBADR_SHIFT, &dev->op_regs->deviceaddr); + + new_addr = USBADR(readl(&dev->op_regs->deviceaddr)); + VDBG(dev, "new_addr = %d\n", new_addr); + } + + done(ep0, req, 0); + + switch (dev->ep0_state) { + case DATA_STATE_XMIT: + /* receive status phase */ + if (prime_status_phase(dev, EP_DIR_OUT)) + ep0_stall(dev); + break; + case DATA_STATE_RECV: + /* send status phase */ + if (prime_status_phase(dev, EP_DIR_IN)) + ep0_stall(dev); + break; + case WAIT_FOR_OUT_STATUS: + dev->ep0_state = WAIT_FOR_SETUP; + break; + case WAIT_FOR_SETUP: + ERROR(dev, "unexpect ep0 packets\n"); + break; + default: + ep0_stall(dev); + break; + } + + VDBG(dev, "<--- %s()\n", __func__); +} + + +/* USB transfer completion interrupt */ +static void handle_trans_complete(struct langwell_udc *dev) +{ + u32 complete_bits; + int i, ep_num, dir, bit_mask, status; + struct langwell_ep *epn; + struct langwell_request *curr_req, *temp_req; + + VDBG(dev, "---> %s()\n", __func__); + + complete_bits = readl(&dev->op_regs->endptcomplete); + VDBG(dev, "endptcomplete register: 0x%08x\n", complete_bits); + + /* Write-Clear the bits in endptcomplete register */ + writel(complete_bits, &dev->op_regs->endptcomplete); + + if (!complete_bits) { + DBG(dev, "complete_bits = 0\n"); + goto done; + } + + for (i = 0; i < dev->ep_max; i++) { + ep_num = i / 2; + dir = i % 2; + + bit_mask = 1 << (ep_num + 16 * dir); + + if (!(complete_bits & bit_mask)) + continue; + + /* ep0 */ + if (i == 1) + epn = &dev->ep[0]; + else + epn = &dev->ep[i]; + + if (epn->name == NULL) { + WARNING(dev, "invalid endpoint\n"); + continue; + } + + if (i < 2) + /* ep0 in and out */ + DBG(dev, "%s-%s transfer completed\n", + epn->name, + is_in(epn) ? "in" : "out"); + else + DBG(dev, "%s transfer completed\n", epn->name); + + /* process the req queue until an uncomplete request */ + list_for_each_entry_safe(curr_req, temp_req, + &epn->queue, queue) { + status = process_ep_req(dev, i, curr_req); + VDBG(dev, "%s req status: %d\n", epn->name, status); + + if (status) + break; + + /* write back status to req */ + curr_req->req.status = status; + + /* ep0 request completion */ + if (ep_num == 0) { + ep0_req_complete(dev, epn, curr_req); + break; + } else { + done(epn, curr_req, status); + } + } + } +done: + VDBG(dev, "<--- %s()\n", __func__); + return; +} + + +/* port change detect interrupt handler */ +static void handle_port_change(struct langwell_udc *dev) +{ + u32 portsc1, devlc; + u32 speed; + + VDBG(dev, "---> %s()\n", __func__); + + if (dev->bus_reset) + dev->bus_reset = 0; + + portsc1 = readl(&dev->op_regs->portsc1); + devlc = readl(&dev->op_regs->devlc); + VDBG(dev, "portsc1 = 0x%08x, devlc = 0x%08x\n", + portsc1, devlc); + + /* bus reset is finished */ + if (!(portsc1 & PORTS_PR)) { + /* get the speed */ + speed = LPM_PSPD(devlc); + switch (speed) { + case LPM_SPEED_HIGH: + dev->gadget.speed = USB_SPEED_HIGH; + break; + case LPM_SPEED_FULL: + dev->gadget.speed = USB_SPEED_FULL; + break; + case LPM_SPEED_LOW: + dev->gadget.speed = USB_SPEED_LOW; + break; + default: + dev->gadget.speed = USB_SPEED_UNKNOWN; + break; + } + VDBG(dev, "speed = %d, dev->gadget.speed = %d\n", + speed, dev->gadget.speed); + } + + /* LPM L0 to L1 */ + if (dev->lpm && dev->lpm_state == LPM_L0) + if (portsc1 & PORTS_SUSP && portsc1 & PORTS_SLP) { + INFO(dev, "LPM L0 to L1\n"); + dev->lpm_state = LPM_L1; + } + + /* LPM L1 to L0, force resume or remote wakeup finished */ + if (dev->lpm && dev->lpm_state == LPM_L1) + if (!(portsc1 & PORTS_SUSP)) { + if (portsc1 & PORTS_SLP) + INFO(dev, "LPM L1 to L0, force resume\n"); + else + INFO(dev, "LPM L1 to L0, remote wakeup\n"); + + dev->lpm_state = LPM_L0; + } + + /* update USB state */ + if (!dev->resume_state) + dev->usb_state = USB_STATE_DEFAULT; + + VDBG(dev, "<--- %s()\n", __func__); +} + + +/* USB reset interrupt handler */ +static void handle_usb_reset(struct langwell_udc *dev) +{ + u32 deviceaddr, + endptsetupstat, + endptcomplete; + unsigned long timeout; + + VDBG(dev, "---> %s()\n", __func__); + + /* Write-Clear the device address */ + deviceaddr = readl(&dev->op_regs->deviceaddr); + writel(deviceaddr & ~USBADR_MASK, &dev->op_regs->deviceaddr); + + dev->dev_addr = 0; + + /* clear usb state */ + dev->resume_state = 0; + + /* LPM L1 to L0, reset */ + if (dev->lpm) + dev->lpm_state = LPM_L0; + + dev->ep0_dir = USB_DIR_OUT; + dev->ep0_state = WAIT_FOR_SETUP; + dev->remote_wakeup = 0; /* default to 0 on reset */ + dev->gadget.b_hnp_enable = 0; + dev->gadget.a_hnp_support = 0; + dev->gadget.a_alt_hnp_support = 0; + + /* Write-Clear all the setup token semaphores */ + endptsetupstat = readl(&dev->op_regs->endptsetupstat); + writel(endptsetupstat, &dev->op_regs->endptsetupstat); + + /* Write-Clear all the endpoint complete status bits */ + endptcomplete = readl(&dev->op_regs->endptcomplete); + writel(endptcomplete, &dev->op_regs->endptcomplete); + + /* wait until all endptprime bits cleared */ + timeout = jiffies + PRIME_TIMEOUT; + while (readl(&dev->op_regs->endptprime)) { + if (time_after(jiffies, timeout)) { + ERROR(dev, "USB reset timeout\n"); + break; + } + cpu_relax(); + } + + /* write 1s to endptflush register to clear any primed buffers */ + writel((u32) ~0, &dev->op_regs->endptflush); + + if (readl(&dev->op_regs->portsc1) & PORTS_PR) { + VDBG(dev, "USB bus reset\n"); + /* bus is reseting */ + dev->bus_reset = 1; + + /* reset all the queues, stop all USB activities */ + stop_activity(dev, dev->driver); + dev->usb_state = USB_STATE_DEFAULT; + } else { + VDBG(dev, "device controller reset\n"); + /* controller reset */ + langwell_udc_reset(dev); + + /* reset all the queues, stop all USB activities */ + stop_activity(dev, dev->driver); + + /* reset ep0 dQH and endptctrl */ + ep0_reset(dev); + + /* enable interrupt and set controller to run state */ + langwell_udc_start(dev); + + dev->usb_state = USB_STATE_ATTACHED; + } + +#ifdef OTG_TRANSCEIVER + /* refer to USB OTG 6.6.2.3 b_hnp_en is cleared */ + if (!dev->lotg->otg.default_a) + dev->lotg->hsm.b_hnp_enable = 0; +#endif + + VDBG(dev, "<--- %s()\n", __func__); +} + + +/* USB bus suspend/resume interrupt */ +static void handle_bus_suspend(struct langwell_udc *dev) +{ + u32 devlc; + DBG(dev, "---> %s()\n", __func__); + + dev->resume_state = dev->usb_state; + dev->usb_state = USB_STATE_SUSPENDED; + +#ifdef OTG_TRANSCEIVER + if (dev->lotg->otg.default_a) { + if (dev->lotg->hsm.b_bus_suspend_vld == 1) { + dev->lotg->hsm.b_bus_suspend = 1; + /* notify transceiver the state changes */ + if (spin_trylock(&dev->lotg->wq_lock)) { + langwell_update_transceiver(); + spin_unlock(&dev->lotg->wq_lock); + } + } + dev->lotg->hsm.b_bus_suspend_vld++; + } else { + if (!dev->lotg->hsm.a_bus_suspend) { + dev->lotg->hsm.a_bus_suspend = 1; + /* notify transceiver the state changes */ + if (spin_trylock(&dev->lotg->wq_lock)) { + langwell_update_transceiver(); + spin_unlock(&dev->lotg->wq_lock); + } + } + } +#endif + + /* report suspend to the driver */ + if (dev->driver) { + if (dev->driver->suspend) { + spin_unlock(&dev->lock); + dev->driver->suspend(&dev->gadget); + spin_lock(&dev->lock); + DBG(dev, "suspend %s\n", dev->driver->driver.name); + } + } + + /* enter PHY low power suspend */ + devlc = readl(&dev->op_regs->devlc); + VDBG(dev, "devlc = 0x%08x\n", devlc); + devlc |= LPM_PHCD; + writel(devlc, &dev->op_regs->devlc); + + DBG(dev, "<--- %s()\n", __func__); +} + + +static void handle_bus_resume(struct langwell_udc *dev) +{ + u32 devlc; + DBG(dev, "---> %s()\n", __func__); + + dev->usb_state = dev->resume_state; + dev->resume_state = 0; + + /* exit PHY low power suspend */ + devlc = readl(&dev->op_regs->devlc); + VDBG(dev, "devlc = 0x%08x\n", devlc); + devlc &= ~LPM_PHCD; + writel(devlc, &dev->op_regs->devlc); + +#ifdef OTG_TRANSCEIVER + if (dev->lotg->otg.default_a == 0) + dev->lotg->hsm.a_bus_suspend = 0; +#endif + + /* report resume to the driver */ + if (dev->driver) { + if (dev->driver->resume) { + spin_unlock(&dev->lock); + dev->driver->resume(&dev->gadget); + spin_lock(&dev->lock); + DBG(dev, "resume %s\n", dev->driver->driver.name); + } + } + + DBG(dev, "<--- %s()\n", __func__); +} + + +/* USB device controller interrupt handler */ +static irqreturn_t langwell_irq(int irq, void *_dev) +{ + struct langwell_udc *dev = _dev; + u32 usbsts, + usbintr, + irq_sts, + portsc1; + + VDBG(dev, "---> %s()\n", __func__); + + if (dev->stopped) { + VDBG(dev, "handle IRQ_NONE\n"); + VDBG(dev, "<--- %s()\n", __func__); + return IRQ_NONE; + } + + spin_lock(&dev->lock); + + /* USB status */ + usbsts = readl(&dev->op_regs->usbsts); + + /* USB interrupt enable */ + usbintr = readl(&dev->op_regs->usbintr); + + irq_sts = usbsts & usbintr; + VDBG(dev, "usbsts = 0x%08x, usbintr = 0x%08x, irq_sts = 0x%08x\n", + usbsts, usbintr, irq_sts); + + if (!irq_sts) { + VDBG(dev, "handle IRQ_NONE\n"); + VDBG(dev, "<--- %s()\n", __func__); + spin_unlock(&dev->lock); + return IRQ_NONE; + } + + /* Write-Clear interrupt status bits */ + writel(irq_sts, &dev->op_regs->usbsts); + + /* resume from suspend */ + portsc1 = readl(&dev->op_regs->portsc1); + if (dev->usb_state == USB_STATE_SUSPENDED) + if (!(portsc1 & PORTS_SUSP)) + handle_bus_resume(dev); + + /* USB interrupt */ + if (irq_sts & STS_UI) { + VDBG(dev, "USB interrupt\n"); + + /* setup packet received from ep0 */ + if (readl(&dev->op_regs->endptsetupstat) + & EP0SETUPSTAT_MASK) { + VDBG(dev, "USB SETUP packet received interrupt\n"); + /* setup tripwire semaphone */ + setup_tripwire(dev); + handle_setup_packet(dev, &dev->local_setup_buff); + } + + /* USB transfer completion */ + if (readl(&dev->op_regs->endptcomplete)) { + VDBG(dev, "USB transfer completion interrupt\n"); + handle_trans_complete(dev); + } + } + + /* SOF received interrupt (for ISO transfer) */ + if (irq_sts & STS_SRI) { + /* FIXME */ + /* VDBG(dev, "SOF received interrupt\n"); */ + } + + /* port change detect interrupt */ + if (irq_sts & STS_PCI) { + VDBG(dev, "port change detect interrupt\n"); + handle_port_change(dev); + } + + /* suspend interrrupt */ + if (irq_sts & STS_SLI) { + VDBG(dev, "suspend interrupt\n"); + handle_bus_suspend(dev); + } + + /* USB reset interrupt */ + if (irq_sts & STS_URI) { + VDBG(dev, "USB reset interrupt\n"); + handle_usb_reset(dev); + } + + /* USB error or system error interrupt */ + if (irq_sts & (STS_UEI | STS_SEI)) { + /* FIXME */ + WARNING(dev, "error IRQ, irq_sts: %x\n", irq_sts); + } + + spin_unlock(&dev->lock); + + VDBG(dev, "<--- %s()\n", __func__); + return IRQ_HANDLED; +} + + +/*-------------------------------------------------------------------------*/ + +/* release device structure */ +static void gadget_release(struct device *_dev) +{ + struct langwell_udc *dev = the_controller; + + DBG(dev, "---> %s()\n", __func__); + + complete(dev->done); + + DBG(dev, "<--- %s()\n", __func__); + kfree(dev); +} + + +/* tear down the binding between this driver and the pci device */ +static void langwell_udc_remove(struct pci_dev *pdev) +{ + struct langwell_udc *dev = the_controller; + + DECLARE_COMPLETION(done); + + BUG_ON(dev->driver); + DBG(dev, "---> %s()\n", __func__); + + dev->done = &done; + + /* free memory allocated in probe */ + if (dev->dtd_pool) + dma_pool_destroy(dev->dtd_pool); + + if (dev->status_req) { + kfree(dev->status_req->req.buf); + kfree(dev->status_req); + } + + if (dev->ep_dqh) + dma_free_coherent(&pdev->dev, dev->ep_dqh_size, + dev->ep_dqh, dev->ep_dqh_dma); + + kfree(dev->ep); + + /* diable IRQ handler */ + if (dev->got_irq) + free_irq(pdev->irq, dev); + +#ifndef OTG_TRANSCEIVER + if (dev->cap_regs) + iounmap(dev->cap_regs); + + if (dev->region) + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + + if (dev->enabled) + pci_disable_device(pdev); +#else + if (dev->transceiver) { + otg_put_transceiver(dev->transceiver); + dev->transceiver = NULL; + dev->lotg = NULL; + } +#endif + + dev->cap_regs = NULL; + + INFO(dev, "unbind\n"); + DBG(dev, "<--- %s()\n", __func__); + + device_unregister(&dev->gadget.dev); + device_remove_file(&pdev->dev, &dev_attr_langwell_udc); + +#ifndef OTG_TRANSCEIVER + pci_set_drvdata(pdev, NULL); +#endif + + /* free dev, wait for the release() finished */ + wait_for_completion(&done); + + the_controller = NULL; +} + + +/* + * wrap this driver around the specified device, but + * don't respond over USB until a gadget driver binds to us. + */ +static int langwell_udc_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct langwell_udc *dev; +#ifndef OTG_TRANSCEIVER + unsigned long resource, len; +#endif + void __iomem *base = NULL; + size_t size; + int retval; + + if (the_controller) { + dev_warn(&pdev->dev, "ignoring\n"); + return -EBUSY; + } + + /* alloc, and start init */ + dev = kzalloc(sizeof *dev, GFP_KERNEL); + if (dev == NULL) { + retval = -ENOMEM; + goto error; + } + + /* initialize device spinlock */ + spin_lock_init(&dev->lock); + + dev->pdev = pdev; + DBG(dev, "---> %s()\n", __func__); + +#ifdef OTG_TRANSCEIVER + /* PCI device is already enabled by otg_transceiver driver */ + dev->enabled = 1; + + /* mem region and register base */ + dev->region = 1; + dev->transceiver = otg_get_transceiver(); + dev->lotg = otg_to_langwell(dev->transceiver); + base = dev->lotg->regs; +#else + pci_set_drvdata(pdev, dev); + + /* now all the pci goodies ... */ + if (pci_enable_device(pdev) < 0) { + retval = -ENODEV; + goto error; + } + dev->enabled = 1; + + /* control register: BAR 0 */ + resource = pci_resource_start(pdev, 0); + len = pci_resource_len(pdev, 0); + if (!request_mem_region(resource, len, driver_name)) { + ERROR(dev, "controller already in use\n"); + retval = -EBUSY; + goto error; + } + dev->region = 1; + + base = ioremap_nocache(resource, len); +#endif + if (base == NULL) { + ERROR(dev, "can't map memory\n"); + retval = -EFAULT; + goto error; + } + + dev->cap_regs = (struct langwell_cap_regs __iomem *) base; + VDBG(dev, "dev->cap_regs: %p\n", dev->cap_regs); + dev->op_regs = (struct langwell_op_regs __iomem *) + (base + OP_REG_OFFSET); + VDBG(dev, "dev->op_regs: %p\n", dev->op_regs); + + /* irq setup after old hardware is cleaned up */ + if (!pdev->irq) { + ERROR(dev, "No IRQ. Check PCI setup!\n"); + retval = -ENODEV; + goto error; + } + +#ifndef OTG_TRANSCEIVER + INFO(dev, "irq %d, io mem: 0x%08lx, len: 0x%08lx, pci mem 0x%p\n", + pdev->irq, resource, len, base); + /* enables bus-mastering for device dev */ + pci_set_master(pdev); + + if (request_irq(pdev->irq, langwell_irq, IRQF_SHARED, + driver_name, dev) != 0) { + ERROR(dev, "request interrupt %d failed\n", pdev->irq); + retval = -EBUSY; + goto error; + } + dev->got_irq = 1; +#endif + + /* set stopped bit */ + dev->stopped = 1; + + /* capabilities and endpoint number */ + dev->lpm = (readl(&dev->cap_regs->hccparams) & HCC_LEN) ? 1 : 0; + dev->dciversion = readw(&dev->cap_regs->dciversion); + dev->devcap = (readl(&dev->cap_regs->dccparams) & DEVCAP) ? 1 : 0; + VDBG(dev, "dev->lpm: %d\n", dev->lpm); + VDBG(dev, "dev->dciversion: 0x%04x\n", dev->dciversion); + VDBG(dev, "dccparams: 0x%08x\n", readl(&dev->cap_regs->dccparams)); + VDBG(dev, "dev->devcap: %d\n", dev->devcap); + if (!dev->devcap) { + ERROR(dev, "can't support device mode\n"); + retval = -ENODEV; + goto error; + } + + /* a pair of endpoints (out/in) for each address */ + dev->ep_max = DEN(readl(&dev->cap_regs->dccparams)) * 2; + VDBG(dev, "dev->ep_max: %d\n", dev->ep_max); + + /* allocate endpoints memory */ + dev->ep = kzalloc(sizeof(struct langwell_ep) * dev->ep_max, + GFP_KERNEL); + if (!dev->ep) { + ERROR(dev, "allocate endpoints memory failed\n"); + retval = -ENOMEM; + goto error; + } + + /* allocate device dQH memory */ + size = dev->ep_max * sizeof(struct langwell_dqh); + VDBG(dev, "orig size = %d\n", size); + if (size < DQH_ALIGNMENT) + size = DQH_ALIGNMENT; + else if ((size % DQH_ALIGNMENT) != 0) { + size += DQH_ALIGNMENT + 1; + size &= ~(DQH_ALIGNMENT - 1); + } + dev->ep_dqh = dma_alloc_coherent(&pdev->dev, size, + &dev->ep_dqh_dma, GFP_KERNEL); + if (!dev->ep_dqh) { + ERROR(dev, "allocate dQH memory failed\n"); + retval = -ENOMEM; + goto error; + } + dev->ep_dqh_size = size; + VDBG(dev, "ep_dqh_size = %d\n", dev->ep_dqh_size); + + /* initialize ep0 status request structure */ + dev->status_req = kzalloc(sizeof(struct langwell_request), GFP_KERNEL); + if (!dev->status_req) { + ERROR(dev, "allocate status_req memory failed\n"); + retval = -ENOMEM; + goto error; + } + INIT_LIST_HEAD(&dev->status_req->queue); + + /* allocate a small amount of memory to get valid address */ + dev->status_req->req.buf = kmalloc(8, GFP_KERNEL); + dev->status_req->req.dma = virt_to_phys(dev->status_req->req.buf); + + dev->resume_state = USB_STATE_NOTATTACHED; + dev->usb_state = USB_STATE_POWERED; + dev->ep0_dir = USB_DIR_OUT; + dev->remote_wakeup = 0; /* default to 0 on reset */ + +#ifndef OTG_TRANSCEIVER + /* reset device controller */ + langwell_udc_reset(dev); +#endif + + /* initialize gadget structure */ + dev->gadget.ops = &langwell_ops; /* usb_gadget_ops */ + dev->gadget.ep0 = &dev->ep[0].ep; /* gadget ep0 */ + INIT_LIST_HEAD(&dev->gadget.ep_list); /* ep_list */ + dev->gadget.speed = USB_SPEED_UNKNOWN; /* speed */ + dev->gadget.is_dualspeed = 1; /* support dual speed */ +#ifdef OTG_TRANSCEIVER + dev->gadget.is_otg = 1; /* support otg mode */ +#endif + + /* the "gadget" abstracts/virtualizes the controller */ + dev_set_name(&dev->gadget.dev, "gadget"); + dev->gadget.dev.parent = &pdev->dev; + dev->gadget.dev.dma_mask = pdev->dev.dma_mask; + dev->gadget.dev.release = gadget_release; + dev->gadget.name = driver_name; /* gadget name */ + + /* controller endpoints reinit */ + eps_reinit(dev); + +#ifndef OTG_TRANSCEIVER + /* reset ep0 dQH and endptctrl */ + ep0_reset(dev); +#endif + + /* create dTD dma_pool resource */ + dev->dtd_pool = dma_pool_create("langwell_dtd", + &dev->pdev->dev, + sizeof(struct langwell_dtd), + DTD_ALIGNMENT, + DMA_BOUNDARY); + + if (!dev->dtd_pool) { + retval = -ENOMEM; + goto error; + } + + /* done */ + INFO(dev, "%s\n", driver_desc); + INFO(dev, "irq %d, pci mem %p\n", pdev->irq, base); + INFO(dev, "Driver version: " DRIVER_VERSION "\n"); + INFO(dev, "Support (max) %d endpoints\n", dev->ep_max); + INFO(dev, "Device interface version: 0x%04x\n", dev->dciversion); + INFO(dev, "Controller mode: %s\n", dev->devcap ? "Device" : "Host"); + INFO(dev, "Support USB LPM: %s\n", dev->lpm ? "Yes" : "No"); + + VDBG(dev, "After langwell_udc_probe(), print all registers:\n"); +#ifdef VERBOSE + print_all_registers(dev); +#endif + + the_controller = dev; + + retval = device_register(&dev->gadget.dev); + if (retval) + goto error; + + retval = device_create_file(&pdev->dev, &dev_attr_langwell_udc); + if (retval) + goto error; + + VDBG(dev, "<--- %s()\n", __func__); + return 0; + +error: + if (dev) { + DBG(dev, "<--- %s()\n", __func__); + langwell_udc_remove(pdev); + } + + return retval; +} + + +/* device controller suspend */ +static int langwell_udc_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct langwell_udc *dev = the_controller; + u32 devlc; + + DBG(dev, "---> %s()\n", __func__); + + /* disable interrupt and set controller to stop state */ + langwell_udc_stop(dev); + + /* diable IRQ handler */ + if (dev->got_irq) + free_irq(pdev->irq, dev); + dev->got_irq = 0; + + + /* save PCI state */ + pci_save_state(pdev); + + /* set device power state */ + pci_set_power_state(pdev, PCI_D3hot); + + /* enter PHY low power suspend */ + devlc = readl(&dev->op_regs->devlc); + VDBG(dev, "devlc = 0x%08x\n", devlc); + devlc |= LPM_PHCD; + writel(devlc, &dev->op_regs->devlc); + + DBG(dev, "<--- %s()\n", __func__); + return 0; +} + + +/* device controller resume */ +static int langwell_udc_resume(struct pci_dev *pdev) +{ + struct langwell_udc *dev = the_controller; + u32 devlc; + + DBG(dev, "---> %s()\n", __func__); + + /* exit PHY low power suspend */ + devlc = readl(&dev->op_regs->devlc); + VDBG(dev, "devlc = 0x%08x\n", devlc); + devlc &= ~LPM_PHCD; + writel(devlc, &dev->op_regs->devlc); + + /* set device D0 power state */ + pci_set_power_state(pdev, PCI_D0); + + /* restore PCI state */ + pci_restore_state(pdev); + + /* enable IRQ handler */ + if (request_irq(pdev->irq, langwell_irq, IRQF_SHARED, driver_name, dev) + != 0) { + ERROR(dev, "request interrupt %d failed\n", pdev->irq); + return -1; + } + dev->got_irq = 1; + + /* reset and start controller to run state */ + if (dev->stopped) { + /* reset device controller */ + langwell_udc_reset(dev); + + /* reset ep0 dQH and endptctrl */ + ep0_reset(dev); + + /* start device if gadget is loaded */ + if (dev->driver) + langwell_udc_start(dev); + } + + /* reset USB status */ + dev->usb_state = USB_STATE_ATTACHED; + dev->ep0_state = WAIT_FOR_SETUP; + dev->ep0_dir = USB_DIR_OUT; + + DBG(dev, "<--- %s()\n", __func__); + return 0; +} + + +/* pci driver shutdown */ +static void langwell_udc_shutdown(struct pci_dev *pdev) +{ + struct langwell_udc *dev = the_controller; + u32 usbmode; + + DBG(dev, "---> %s()\n", __func__); + + /* reset controller mode to IDLE */ + usbmode = readl(&dev->op_regs->usbmode); + DBG(dev, "usbmode = 0x%08x\n", usbmode); + usbmode &= (~3 | MODE_IDLE); + writel(usbmode, &dev->op_regs->usbmode); + + DBG(dev, "<--- %s()\n", __func__); +} + +/*-------------------------------------------------------------------------*/ + +static const struct pci_device_id pci_ids[] = { { + .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class_mask = ~0, + .vendor = 0x8086, + .device = 0x0811, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, +}, { /* end: all zeroes */ } +}; + + +MODULE_DEVICE_TABLE(pci, pci_ids); + + +static struct pci_driver langwell_pci_driver = { + .name = (char *) driver_name, + .id_table = pci_ids, + + .probe = langwell_udc_probe, + .remove = langwell_udc_remove, + + /* device controller suspend/resume */ + .suspend = langwell_udc_suspend, + .resume = langwell_udc_resume, + + .shutdown = langwell_udc_shutdown, +}; + + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Xiaochen Shen <xiaochen.shen@intel.com>"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); + + +static int __init init(void) +{ +#ifdef OTG_TRANSCEIVER + return langwell_register_peripheral(&langwell_pci_driver); +#else + return pci_register_driver(&langwell_pci_driver); +#endif +} +module_init(init); + + +static void __exit cleanup(void) +{ +#ifdef OTG_TRANSCEIVER + return langwell_unregister_peripheral(&langwell_pci_driver); +#else + pci_unregister_driver(&langwell_pci_driver); +#endif +} +module_exit(cleanup); + diff --git a/drivers/usb/gadget/langwell_udc.h b/drivers/usb/gadget/langwell_udc.h new file mode 100644 index 000000000000..9719934e1c08 --- /dev/null +++ b/drivers/usb/gadget/langwell_udc.h @@ -0,0 +1,228 @@ +/* + * Intel Langwell USB Device Controller driver + * Copyright (C) 2008-2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <linux/usb/langwell_udc.h> + +#if defined(CONFIG_USB_LANGWELL_OTG) +#include <linux/usb/langwell_otg.h> +#endif + + +/*-------------------------------------------------------------------------*/ + +/* driver data structures and utilities */ + +/* + * dTD: Device Endpoint Transfer Descriptor + * describe to the device controller the location and quantity of + * data to be send/received for given transfer + */ +struct langwell_dtd { + u32 dtd_next; +/* bits 31:5, next transfer element pointer */ +#define DTD_NEXT(d) (((d)>>5)&0x7ffffff) +#define DTD_NEXT_MASK (0x7ffffff << 5) +/* terminate */ +#define DTD_TERM BIT(0) + /* bits 7:0, execution back states */ + u32 dtd_status:8; +#define DTD_STATUS(d) (((d)>>0)&0xff) +#define DTD_STS_ACTIVE BIT(7) /* active */ +#define DTD_STS_HALTED BIT(6) /* halted */ +#define DTD_STS_DBE BIT(5) /* data buffer error */ +#define DTD_STS_TRE BIT(3) /* transaction error */ + /* bits 9:8 */ + u32 dtd_res0:2; + /* bits 11:10, multipier override */ + u32 dtd_multo:2; +#define DTD_MULTO (BIT(11) | BIT(10)) + /* bits 14:12 */ + u32 dtd_res1:3; + /* bit 15, interrupt on complete */ + u32 dtd_ioc:1; +#define DTD_IOC BIT(15) + /* bits 30:16, total bytes */ + u32 dtd_total:15; +#define DTD_TOTAL(d) (((d)>>16)&0x7fff) +#define DTD_MAX_TRANSFER_LENGTH 0x4000 + /* bit 31 */ + u32 dtd_res2:1; + /* dTD buffer pointer page 0 to 4 */ + u32 dtd_buf[5]; +#define DTD_OFFSET_MASK 0xfff +/* bits 31:12, buffer pointer */ +#define DTD_BUFFER(d) (((d)>>12)&0x3ff) +/* bits 11:0, current offset */ +#define DTD_C_OFFSET(d) (((d)>>0)&0xfff) +/* bits 10:0, frame number */ +#define DTD_FRAME(d) (((d)>>0)&0x7ff) + + /* driver-private parts */ + + /* dtd dma address */ + dma_addr_t dtd_dma; + /* next dtd virtual address */ + struct langwell_dtd *next_dtd_virt; +}; + + +/* + * dQH: Device Endpoint Queue Head + * describe where all transfers are managed + * 48-byte data structure, aligned on 64-byte boundary + * + * These are associated with dTD structure + */ +struct langwell_dqh { + /* endpoint capabilities and characteristics */ + u32 dqh_res0:15; /* bits 14:0 */ + u32 dqh_ios:1; /* bit 15, interrupt on setup */ +#define DQH_IOS BIT(15) + u32 dqh_mpl:11; /* bits 26:16, maximum packet length */ +#define DQH_MPL (0x7ff << 16) + u32 dqh_res1:2; /* bits 28:27 */ + u32 dqh_zlt:1; /* bit 29, zero length termination */ +#define DQH_ZLT BIT(29) + u32 dqh_mult:2; /* bits 31:30 */ +#define DQH_MULT (BIT(30) | BIT(31)) + + /* current dTD pointer */ + u32 dqh_current; /* locate the transfer in progress */ +#define DQH_C_DTD(e) \ + (((e)>>5)&0x7ffffff) /* bits 31:5, current dTD pointer */ + + /* transfer overlay, hardware parts of a struct langwell_dtd */ + u32 dtd_next; + u32 dtd_status:8; /* bits 7:0, execution back states */ + u32 dtd_res0:2; /* bits 9:8 */ + u32 dtd_multo:2; /* bits 11:10, multipier override */ + u32 dtd_res1:3; /* bits 14:12 */ + u32 dtd_ioc:1; /* bit 15, interrupt on complete */ + u32 dtd_total:15; /* bits 30:16, total bytes */ + u32 dtd_res2:1; /* bit 31 */ + u32 dtd_buf[5]; /* dTD buffer pointer page 0 to 4 */ + + u32 dqh_res2; + struct usb_ctrlrequest dqh_setup; /* setup packet buffer */ +} __attribute__ ((aligned(64))); + + +/* endpoint data structure */ +struct langwell_ep { + struct usb_ep ep; + dma_addr_t dma; + struct langwell_udc *dev; + unsigned long irqs; + struct list_head queue; + struct langwell_dqh *dqh; + const struct usb_endpoint_descriptor *desc; + char name[14]; + unsigned stopped:1, + ep_type:2, + ep_num:8; +}; + + +/* request data structure */ +struct langwell_request { + struct usb_request req; + struct langwell_dtd *dtd, *head, *tail; + struct langwell_ep *ep; + dma_addr_t dtd_dma; + struct list_head queue; + unsigned dtd_count; + unsigned mapped:1; +}; + + +/* ep0 transfer state */ +enum ep0_state { + WAIT_FOR_SETUP, + DATA_STATE_XMIT, + DATA_STATE_NEED_ZLP, + WAIT_FOR_OUT_STATUS, + DATA_STATE_RECV, +}; + + +/* device suspend state */ +enum lpm_state { + LPM_L0, /* on */ + LPM_L1, /* LPM L1 sleep */ + LPM_L2, /* suspend */ + LPM_L3, /* off */ +}; + + +/* device data structure */ +struct langwell_udc { + /* each pci device provides one gadget, several endpoints */ + struct usb_gadget gadget; + spinlock_t lock; /* device lock */ + struct langwell_ep *ep; + struct usb_gadget_driver *driver; + struct otg_transceiver *transceiver; + u8 dev_addr; + u32 usb_state; + u32 resume_state; + u32 bus_reset; + enum lpm_state lpm_state; + enum ep0_state ep0_state; + u32 ep0_dir; + u16 dciversion; + unsigned ep_max; + unsigned devcap:1, + enabled:1, + region:1, + got_irq:1, + powered:1, + remote_wakeup:1, + rate:1, + is_reset:1, + softconnected:1, + vbus_active:1, + suspended:1, + stopped:1, + lpm:1; /* LPM capability */ + + /* pci state used to access those endpoints */ + struct pci_dev *pdev; + + /* Langwell otg transceiver */ + struct langwell_otg *lotg; + + /* control registers */ + struct langwell_cap_regs __iomem *cap_regs; + struct langwell_op_regs __iomem *op_regs; + + struct usb_ctrlrequest local_setup_buff; + struct langwell_dqh *ep_dqh; + size_t ep_dqh_size; + dma_addr_t ep_dqh_dma; + + /* ep0 status request */ + struct langwell_request *status_req; + + /* dma pool */ + struct dma_pool *dtd_pool; + + /* make sure release() is done */ + struct completion *done; +}; + diff --git a/include/linux/usb/langwell_udc.h b/include/linux/usb/langwell_udc.h new file mode 100644 index 000000000000..c949178a6530 --- /dev/null +++ b/include/linux/usb/langwell_udc.h @@ -0,0 +1,310 @@ +/* + * Intel Langwell USB Device Controller driver + * Copyright (C) 2008-2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef __LANGWELL_UDC_H +#define __LANGWELL_UDC_H + + +/* MACRO defines */ +#define CAP_REG_OFFSET 0x0 +#define OP_REG_OFFSET 0x28 + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +#define DQH_ALIGNMENT 2048 +#define DTD_ALIGNMENT 64 +#define DMA_BOUNDARY 4096 + +#define EP0_MAX_PKT_SIZE 64 +#define EP_DIR_IN 1 +#define EP_DIR_OUT 0 + +#define FLUSH_TIMEOUT 1000 +#define RESET_TIMEOUT 1000 +#define SETUPSTAT_TIMEOUT 100 +#define PRIME_TIMEOUT 100 + + +/* device memory space registers */ + +/* Capability Registers, BAR0 + CAP_REG_OFFSET */ +struct langwell_cap_regs { + /* offset: 0x0 */ + u8 caplength; /* offset of Operational Register */ + u8 _reserved3; + u16 hciversion; /* H: BCD encoding of host version */ + u32 hcsparams; /* H: host port steering logic capability */ + u32 hccparams; /* H: host multiple mode control capability */ +#define HCC_LEN BIT(17) /* Link power management (LPM) capability */ + u8 _reserved4[0x20-0xc]; + /* offset: 0x20 */ + u16 dciversion; /* BCD encoding of device version */ + u8 _reserved5[0x24-0x22]; + u32 dccparams; /* overall device controller capability */ +#define HOSTCAP BIT(8) /* host capable */ +#define DEVCAP BIT(7) /* device capable */ +#define DEN(d) \ + (((d)>>0)&0x1f) /* bits 4:0, device endpoint number */ +} __attribute__ ((packed)); + + +/* Operational Registers, BAR0 + OP_REG_OFFSET */ +struct langwell_op_regs { + /* offset: 0x28 */ + u32 extsts; +#define EXTS_TI1 BIT(4) /* general purpose timer interrupt 1 */ +#define EXTS_TI1TI0 BIT(3) /* general purpose timer interrupt 0 */ +#define EXTS_TI1UPI BIT(2) /* USB host periodic interrupt */ +#define EXTS_TI1UAI BIT(1) /* USB host asynchronous interrupt */ +#define EXTS_TI1NAKI BIT(0) /* NAK interrupt */ + u32 extintr; +#define EXTI_TIE1 BIT(4) /* general purpose timer interrupt enable 1 */ +#define EXTI_TIE0 BIT(3) /* general purpose timer interrupt enable 0 */ +#define EXTI_UPIE BIT(2) /* USB host periodic interrupt enable */ +#define EXTI_UAIE BIT(1) /* USB host asynchronous interrupt enable */ +#define EXTI_NAKE BIT(0) /* NAK interrupt enable */ + /* offset: 0x30 */ + u32 usbcmd; +#define CMD_HIRD(u) \ + (((u)>>24)&0xf) /* bits 27:24, host init resume duration */ +#define CMD_ITC(u) \ + (((u)>>16)&0xff) /* bits 23:16, interrupt threshold control */ +#define CMD_PPE BIT(15) /* per-port change events enable */ +#define CMD_ATDTW BIT(14) /* add dTD tripwire */ +#define CMD_SUTW BIT(13) /* setup tripwire */ +#define CMD_ASPE BIT(11) /* asynchronous schedule park mode enable */ +#define CMD_FS2 BIT(10) /* frame list size */ +#define CMD_ASP1 BIT(9) /* asynchronous schedule park mode count */ +#define CMD_ASP0 BIT(8) +#define CMD_LR BIT(7) /* light host/device controller reset */ +#define CMD_IAA BIT(6) /* interrupt on async advance doorbell */ +#define CMD_ASE BIT(5) /* asynchronous schedule enable */ +#define CMD_PSE BIT(4) /* periodic schedule enable */ +#define CMD_FS1 BIT(3) +#define CMD_FS0 BIT(2) +#define CMD_RST BIT(1) /* controller reset */ +#define CMD_RUNSTOP BIT(0) /* run/stop */ + u32 usbsts; +#define STS_PPCI(u) \ + (((u)>>16)&0xffff) /* bits 31:16, port-n change detect */ +#define STS_AS BIT(15) /* asynchronous schedule status */ +#define STS_PS BIT(14) /* periodic schedule status */ +#define STS_RCL BIT(13) /* reclamation */ +#define STS_HCH BIT(12) /* HC halted */ +#define STS_ULPII BIT(10) /* ULPI interrupt */ +#define STS_SLI BIT(8) /* DC suspend */ +#define STS_SRI BIT(7) /* SOF received */ +#define STS_URI BIT(6) /* USB reset received */ +#define STS_AAI BIT(5) /* interrupt on async advance */ +#define STS_SEI BIT(4) /* system error */ +#define STS_FRI BIT(3) /* frame list rollover */ +#define STS_PCI BIT(2) /* port change detect */ +#define STS_UEI BIT(1) /* USB error interrupt */ +#define STS_UI BIT(0) /* USB interrupt */ + u32 usbintr; +/* bits 31:16, per-port interrupt enable */ +#define INTR_PPCE(u) (((u)>>16)&0xffff) +#define INTR_ULPIE BIT(10) /* ULPI enable */ +#define INTR_SLE BIT(8) /* DC sleep/suspend enable */ +#define INTR_SRE BIT(7) /* SOF received enable */ +#define INTR_URE BIT(6) /* USB reset enable */ +#define INTR_AAE BIT(5) /* interrupt on async advance enable */ +#define INTR_SEE BIT(4) /* system error enable */ +#define INTR_FRE BIT(3) /* frame list rollover enable */ +#define INTR_PCE BIT(2) /* port change detect enable */ +#define INTR_UEE BIT(1) /* USB error interrupt enable */ +#define INTR_UE BIT(0) /* USB interrupt enable */ + u32 frindex; /* frame index */ +#define FRINDEX_MASK (0x3fff << 0) + u32 ctrldssegment; /* not used */ + u32 deviceaddr; +#define USBADR_SHIFT 25 +#define USBADR(d) \ + (((d)>>25)&0x7f) /* bits 31:25, device address */ +#define USBADR_MASK (0x7f << 25) +#define USBADRA BIT(24) /* device address advance */ + u32 endpointlistaddr;/* endpoint list top memory address */ +/* bits 31:11, endpoint list pointer */ +#define EPBASE(d) (((d)>>11)&0x1fffff) +#define ENDPOINTLISTADDR_MASK (0x1fffff << 11) + u32 ttctrl; /* H: TT operatin, not used */ + /* offset: 0x50 */ + u32 burstsize; /* burst size of data movement */ +#define TXPBURST(b) \ + (((b)>>8)&0xff) /* bits 15:8, TX burst length */ +#define RXPBURST(b) \ + (((b)>>0)&0xff) /* bits 7:0, RX burst length */ + u32 txfilltuning; /* TX tuning */ + u32 txttfilltuning; /* H: TX TT tuning */ + u32 ic_usb; /* control the IC_USB FS/LS transceiver */ + /* offset: 0x60 */ + u32 ulpi_viewport; /* indirect access to ULPI PHY */ +#define ULPIWU BIT(31) /* ULPI wakeup */ +#define ULPIRUN BIT(30) /* ULPI read/write run */ +#define ULPIRW BIT(29) /* ULPI read/write control */ +#define ULPISS BIT(27) /* ULPI sync state */ +#define ULPIPORT(u) \ + (((u)>>24)&7) /* bits 26:24, ULPI port number */ +#define ULPIADDR(u) \ + (((u)>>16)&0xff) /* bits 23:16, ULPI data address */ +#define ULPIDATRD(u) \ + (((u)>>8)&0xff) /* bits 15:8, ULPI data read */ +#define ULPIDATWR(u) \ + (((u)>>0)&0xff) /* bits 7:0, ULPI date write */ + u8 _reserved6[0x70-0x64]; + /* offset: 0x70 */ + u32 configflag; /* H: not used */ + u32 portsc1; /* port status */ +#define DA(p) \ + (((p)>>25)&0x7f) /* bits 31:25, device address */ +#define PORTS_SSTS (BIT(24) | BIT(23)) /* suspend status */ +#define PORTS_WKOC BIT(22) /* wake on over-current enable */ +#define PORTS_WKDS BIT(21) /* wake on disconnect enable */ +#define PORTS_WKCN BIT(20) /* wake on connect enable */ +#define PORTS_PTC(p) (((p)>>16)&0xf) /* bits 19:16, port test control */ +#define PORTS_PIC (BIT(15) | BIT(14)) /* port indicator control */ +#define PORTS_PO BIT(13) /* port owner */ +#define PORTS_PP BIT(12) /* port power */ +#define PORTS_LS (BIT(11) | BIT(10)) /* line status */ +#define PORTS_SLP BIT(9) /* suspend using L1 */ +#define PORTS_PR BIT(8) /* port reset */ +#define PORTS_SUSP BIT(7) /* suspend */ +#define PORTS_FPR BIT(6) /* force port resume */ +#define PORTS_OCC BIT(5) /* over-current change */ +#define PORTS_OCA BIT(4) /* over-current active */ +#define PORTS_PEC BIT(3) /* port enable/disable change */ +#define PORTS_PE BIT(2) /* port enable/disable */ +#define PORTS_CSC BIT(1) /* connect status change */ +#define PORTS_CCS BIT(0) /* current connect status */ + u8 _reserved7[0xb4-0x78]; + /* offset: 0xb4 */ + u32 devlc; /* control LPM and each USB port behavior */ +/* bits 31:29, parallel transceiver select */ +#define LPM_PTS(d) (((d)>>29)&7) +#define LPM_STS BIT(28) /* serial transceiver select */ +#define LPM_PTW BIT(27) /* parallel transceiver width */ +#define LPM_PSPD(d) (((d)>>25)&3) /* bits 26:25, port speed */ +#define LPM_PSPD_MASK (BIT(26) | BIT(25)) +#define LPM_SPEED_FULL 0 +#define LPM_SPEED_LOW 1 +#define LPM_SPEED_HIGH 2 +#define LPM_SRT BIT(24) /* shorten reset time */ +#define LPM_PFSC BIT(23) /* port force full speed connect */ +#define LPM_PHCD BIT(22) /* PHY low power suspend clock disable */ +#define LPM_STL BIT(16) /* STALL reply to LPM token */ +#define LPM_BA(d) \ + (((d)>>1)&0x7ff) /* bits 11:1, BmAttributes */ +#define LPM_NYT_ACK BIT(0) /* NYET/ACK reply to LPM token */ + u8 _reserved8[0xf4-0xb8]; + /* offset: 0xf4 */ + u32 otgsc; /* On-The-Go status and control */ +#define OTGSC_DPIE BIT(30) /* data pulse interrupt enable */ +#define OTGSC_MSE BIT(29) /* 1 ms timer interrupt enable */ +#define OTGSC_BSEIE BIT(28) /* B session end interrupt enable */ +#define OTGSC_BSVIE BIT(27) /* B session valid interrupt enable */ +#define OTGSC_ASVIE BIT(26) /* A session valid interrupt enable */ +#define OTGSC_AVVIE BIT(25) /* A VBUS valid interrupt enable */ +#define OTGSC_IDIE BIT(24) /* USB ID interrupt enable */ +#define OTGSC_DPIS BIT(22) /* data pulse interrupt status */ +#define OTGSC_MSS BIT(21) /* 1 ms timer interrupt status */ +#define OTGSC_BSEIS BIT(20) /* B session end interrupt status */ +#define OTGSC_BSVIS BIT(19) /* B session valid interrupt status */ +#define OTGSC_ASVIS BIT(18) /* A session valid interrupt status */ +#define OTGSC_AVVIS BIT(17) /* A VBUS valid interrupt status */ +#define OTGSC_IDIS BIT(16) /* USB ID interrupt status */ +#define OTGSC_DPS BIT(14) /* data bus pulsing status */ +#define OTGSC_MST BIT(13) /* 1 ms timer toggle */ +#define OTGSC_BSE BIT(12) /* B session end */ +#define OTGSC_BSV BIT(11) /* B session valid */ +#define OTGSC_ASV BIT(10) /* A session valid */ +#define OTGSC_AVV BIT(9) /* A VBUS valid */ +#define OTGSC_USBID BIT(8) /* USB ID */ +#define OTGSC_HABA BIT(7) /* hw assist B-disconnect to A-connect */ +#define OTGSC_HADP BIT(6) /* hw assist data pulse */ +#define OTGSC_IDPU BIT(5) /* ID pullup */ +#define OTGSC_DP BIT(4) /* data pulsing */ +#define OTGSC_OT BIT(3) /* OTG termination */ +#define OTGSC_HAAR BIT(2) /* hw assist auto reset */ +#define OTGSC_VC BIT(1) /* VBUS charge */ +#define OTGSC_VD BIT(0) /* VBUS discharge */ + u32 usbmode; +#define MODE_VBPS BIT(5) /* R/W VBUS power select */ +#define MODE_SDIS BIT(4) /* R/W stream disable mode */ +#define MODE_SLOM BIT(3) /* R/W setup lockout mode */ +#define MODE_ENSE BIT(2) /* endian select */ +#define MODE_CM(u) (((u)>>0)&3) /* bits 1:0, controller mode */ +#define MODE_IDLE 0 +#define MODE_DEVICE 2 +#define MODE_HOST 3 + u8 _reserved9[0x100-0xfc]; + /* offset: 0x100 */ + u32 endptnak; +#define EPTN(e) \ + (((e)>>16)&0xffff) /* bits 31:16, TX endpoint NAK */ +#define EPRN(e) \ + (((e)>>0)&0xffff) /* bits 15:0, RX endpoint NAK */ + u32 endptnaken; +#define EPTNE(e) \ + (((e)>>16)&0xffff) /* bits 31:16, TX endpoint NAK enable */ +#define EPRNE(e) \ + (((e)>>0)&0xffff) /* bits 15:0, RX endpoint NAK enable */ + u32 endptsetupstat; +#define SETUPSTAT_MASK (0xffff << 0) /* bits 15:0 */ +#define EP0SETUPSTAT_MASK 1 + u32 endptprime; +/* bits 31:16, prime endpoint transmit buffer */ +#define PETB(e) (((e)>>16)&0xffff) +/* bits 15:0, prime endpoint receive buffer */ +#define PERB(e) (((e)>>0)&0xffff) + /* offset: 0x110 */ + u32 endptflush; +/* bits 31:16, flush endpoint transmit buffer */ +#define FETB(e) (((e)>>16)&0xffff) +/* bits 15:0, flush endpoint receive buffer */ +#define FERB(e) (((e)>>0)&0xffff) + u32 endptstat; +/* bits 31:16, endpoint transmit buffer ready */ +#define ETBR(e) (((e)>>16)&0xffff) +/* bits 15:0, endpoint receive buffer ready */ +#define ERBR(e) (((e)>>0)&0xffff) + u32 endptcomplete; +/* bits 31:16, endpoint transmit complete event */ +#define ETCE(e) (((e)>>16)&0xffff) +/* bits 15:0, endpoint receive complete event */ +#define ERCE(e) (((e)>>0)&0xffff) + /* offset: 0x11c */ + u32 endptctrl[16]; +#define EPCTRL_TXE BIT(23) /* TX endpoint enable */ +#define EPCTRL_TXR BIT(22) /* TX data toggle reset */ +#define EPCTRL_TXI BIT(21) /* TX data toggle inhibit */ +#define EPCTRL_TXT(e) (((e)>>18)&3) /* bits 19:18, TX endpoint type */ +#define EPCTRL_TXT_SHIFT 18 +#define EPCTRL_TXD BIT(17) /* TX endpoint data source */ +#define EPCTRL_TXS BIT(16) /* TX endpoint STALL */ +#define EPCTRL_RXE BIT(7) /* RX endpoint enable */ +#define EPCTRL_RXR BIT(6) /* RX data toggle reset */ +#define EPCTRL_RXI BIT(5) /* RX data toggle inhibit */ +#define EPCTRL_RXT(e) (((e)>>2)&3) /* bits 3:2, RX endpoint type */ +#define EPCTRL_RXT_SHIFT 2 /* bits 19:18, TX endpoint type */ +#define EPCTRL_RXD BIT(1) /* RX endpoint data sink */ +#define EPCTRL_RXS BIT(0) /* RX endpoint STALL */ +} __attribute__ ((packed)); + +#endif /* __LANGWELL_UDC_H */ + -- cgit v1.2.3 From 453f77558810ffa669ed5a510a7173ec49def396 Mon Sep 17 00:00:00 2001 From: Hao Wu <hao.wu@intel.com> Date: Thu, 4 Jun 2009 16:06:50 +0800 Subject: USB: Add Intel Langwell USB OTG Transceiver Drive Description: This driver is used for Intel Langwell* USB OTG controller in Intel Moorestown* platform. It tries to implement host/device role switch according to OTG spec. The actual hsot and device functions are accomplished in modified EHCI driver and Intel Langwell USB OTG client controller driver. * Langwell and Moorestown are names used in development. They are not approved official name. Note: This patch is the first version Intel Langwell USB OTG Transceiver driver. The development is not finished, and the bug fixing is on going for some hardware and software issues. The main purpose of this submission is for code view. Supported features: - Data-line Pulsing SRP - Support HNP to switch roles - PCI D0/D3 power management support Known issues: - HNP is only tested with another Moorestown platform. - PCI D0/D3 power management support is not fully tested. - VBus Pulsing SRP is not support in current version. Signed-off-by: Hao Wu <hao.wu@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/otg/Kconfig | 14 + drivers/usb/otg/Makefile | 1 + drivers/usb/otg/langwell_otg.c | 1915 ++++++++++++++++++++++++++++++++++++++ include/linux/usb/langwell_otg.h | 177 ++++ 4 files changed, 2107 insertions(+) create mode 100644 drivers/usb/otg/langwell_otg.c create mode 100644 include/linux/usb/langwell_otg.h (limited to 'drivers') diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index aa884d072f0b..69feeec1628c 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -59,4 +59,18 @@ config NOP_USB_XCEIV built-in with usb ip or which are autonomous and doesn't require any phy programming such as ISP1x04 etc. +config USB_LANGWELL_OTG + tristate "Intel Langwell USB OTG dual-role support" + depends on USB && MRST + select USB_OTG + select USB_OTG_UTILS + help + Say Y here if you want to build Intel Langwell USB OTG + transciever driver in kernel. This driver implements role + switch between EHCI host driver and Langwell USB OTG + client driver. + + To compile this driver as a module, choose M here: the + module will be called langwell_otg. + endif # USB || OTG diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index 208167856529..6d1abdd3c0ac 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_USB_OTG_UTILS) += otg.o obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o obj-$(CONFIG_TWL4030_USB) += twl4030-usb.o +obj-$(CONFIG_USB_LANGWELL_OTG) += langwell_otg.o obj-$(CONFIG_NOP_USB_XCEIV) += nop-usb-xceiv.o ccflags-$(CONFIG_USB_DEBUG) += -DDEBUG diff --git a/drivers/usb/otg/langwell_otg.c b/drivers/usb/otg/langwell_otg.c new file mode 100644 index 000000000000..6f628d0e9f39 --- /dev/null +++ b/drivers/usb/otg/langwell_otg.c @@ -0,0 +1,1915 @@ +/* + * Intel Langwell USB OTG transceiver driver + * Copyright (C) 2008 - 2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +/* This driver helps to switch Langwell OTG controller function between host + * and peripheral. It works with EHCI driver and Langwell client controller + * driver together. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/moduleparam.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb.h> +#include <linux/usb/otg.h> +#include <linux/notifier.h> +#include <asm/ipc_defs.h> +#include <linux/delay.h> +#include "../core/hcd.h" + +#include <linux/usb/langwell_otg.h> + +#define DRIVER_DESC "Intel Langwell USB OTG transceiver driver" +#define DRIVER_VERSION "3.0.0.32L.0002" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Henry Yuan <hang.yuan@intel.com>, Hao Wu <hao.wu@intel.com>"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); + +static const char driver_name[] = "langwell_otg"; + +static int langwell_otg_probe(struct pci_dev *pdev, + const struct pci_device_id *id); +static void langwell_otg_remove(struct pci_dev *pdev); +static int langwell_otg_suspend(struct pci_dev *pdev, pm_message_t message); +static int langwell_otg_resume(struct pci_dev *pdev); + +static int langwell_otg_set_host(struct otg_transceiver *otg, + struct usb_bus *host); +static int langwell_otg_set_peripheral(struct otg_transceiver *otg, + struct usb_gadget *gadget); +static int langwell_otg_start_srp(struct otg_transceiver *otg); + +static const struct pci_device_id pci_ids[] = {{ + .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class_mask = ~0, + .vendor = 0x8086, + .device = 0x0811, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, +}, { /* end: all zeroes */ } +}; + +static struct pci_driver otg_pci_driver = { + .name = (char *) driver_name, + .id_table = pci_ids, + + .probe = langwell_otg_probe, + .remove = langwell_otg_remove, + + .suspend = langwell_otg_suspend, + .resume = langwell_otg_resume, +}; + +static const char *state_string(enum usb_otg_state state) +{ + switch (state) { + case OTG_STATE_A_IDLE: + return "a_idle"; + case OTG_STATE_A_WAIT_VRISE: + return "a_wait_vrise"; + case OTG_STATE_A_WAIT_BCON: + return "a_wait_bcon"; + case OTG_STATE_A_HOST: + return "a_host"; + case OTG_STATE_A_SUSPEND: + return "a_suspend"; + case OTG_STATE_A_PERIPHERAL: + return "a_peripheral"; + case OTG_STATE_A_WAIT_VFALL: + return "a_wait_vfall"; + case OTG_STATE_A_VBUS_ERR: + return "a_vbus_err"; + case OTG_STATE_B_IDLE: + return "b_idle"; + case OTG_STATE_B_SRP_INIT: + return "b_srp_init"; + case OTG_STATE_B_PERIPHERAL: + return "b_peripheral"; + case OTG_STATE_B_WAIT_ACON: + return "b_wait_acon"; + case OTG_STATE_B_HOST: + return "b_host"; + default: + return "UNDEFINED"; + } +} + +/* HSM timers */ +static inline struct langwell_otg_timer *otg_timer_initializer +(void (*function)(unsigned long), unsigned long expires, unsigned long data) +{ + struct langwell_otg_timer *timer; + timer = kmalloc(sizeof(struct langwell_otg_timer), GFP_KERNEL); + timer->function = function; + timer->expires = expires; + timer->data = data; + return timer; +} + +static struct langwell_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, + *a_aidl_bdis_tmr, *b_ase0_brst_tmr, *b_se0_srp_tmr, *b_srp_res_tmr, + *b_bus_suspend_tmr; + +static struct list_head active_timers; + +static struct langwell_otg *the_transceiver; + +/* host/client notify transceiver when event affects HNP state */ +void langwell_update_transceiver() +{ + otg_dbg("transceiver driver is notified\n"); + queue_work(the_transceiver->qwork, &the_transceiver->work); +} +EXPORT_SYMBOL(langwell_update_transceiver); + +static int langwell_otg_set_host(struct otg_transceiver *otg, + struct usb_bus *host) +{ + otg->host = host; + + return 0; +} + +static int langwell_otg_set_peripheral(struct otg_transceiver *otg, + struct usb_gadget *gadget) +{ + otg->gadget = gadget; + + return 0; +} + +static int langwell_otg_set_power(struct otg_transceiver *otg, + unsigned mA) +{ + return 0; +} + +/* A-device drives vbus, controlled through PMIC CHRGCNTL register*/ +static void langwell_otg_drv_vbus(int on) +{ + struct ipc_pmic_reg_data pmic_data = {0}; + struct ipc_pmic_reg_data battery_data; + + /* Check if battery is attached or not */ + battery_data.pmic_reg_data[0].register_address = 0xd2; + battery_data.ioc = 0; + battery_data.num_entries = 1; + if (ipc_pmic_register_read(&battery_data)) { + otg_dbg("Failed to read PMIC register 0xd2.\n"); + return; + } + + if ((battery_data.pmic_reg_data[0].value & 0x20) == 0) { + otg_dbg("no battery attached\n"); + return; + } + + /* Workaround for battery attachment issue */ + if (battery_data.pmic_reg_data[0].value == 0x34) { + otg_dbg("battery \n"); + return; + } + + otg_dbg("battery attached\n"); + + pmic_data.ioc = 0; + pmic_data.pmic_reg_data[0].register_address = 0xD4; + pmic_data.num_entries = 1; + if (on) + pmic_data.pmic_reg_data[0].value = 0x20; + else + pmic_data.pmic_reg_data[0].value = 0xc0; + + if (ipc_pmic_register_write(&pmic_data, TRUE)) + otg_dbg("Failed to write PMIC.\n"); + +} + +/* charge vbus or discharge vbus through a resistor to ground */ +static void langwell_otg_chrg_vbus(int on) +{ + + u32 val; + + val = readl(the_transceiver->regs + CI_OTGSC); + + if (on) + writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_VC, + the_transceiver->regs + CI_OTGSC); + else + writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_VD, + the_transceiver->regs + CI_OTGSC); + +} + +/* Start SRP */ +static int langwell_otg_start_srp(struct otg_transceiver *otg) +{ + u32 val; + + otg_dbg("Start SRP ->\n"); + + val = readl(the_transceiver->regs + CI_OTGSC); + + writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_HADP, + the_transceiver->regs + CI_OTGSC); + + /* Check if the data plus is finished or not */ + msleep(8); + val = readl(the_transceiver->regs + CI_OTGSC); + if (val & (OTGSC_HADP | OTGSC_DP)) + otg_dbg("DataLine SRP Error\n"); + + /* FIXME: VBus SRP */ + + return 0; +} + + +/* stop SOF via bus_suspend */ +static void langwell_otg_loc_sof(int on) +{ + struct usb_hcd *hcd; + int err; + + otg_dbg("loc_sof -> %d\n", on); + + hcd = bus_to_hcd(the_transceiver->otg.host); + if (on) + err = hcd->driver->bus_resume(hcd); + else + err = hcd->driver->bus_suspend(hcd); + + if (err) + otg_dbg("Failed to resume/suspend bus - %d\n", err); +} + +static void langwell_otg_phy_low_power(int on) +{ + u32 val; + + otg_dbg("phy low power mode-> %d\n", on); + + val = readl(the_transceiver->regs + CI_HOSTPC1); + if (on) + writel(val | HOSTPC1_PHCD, the_transceiver->regs + CI_HOSTPC1); + else + writel(val & ~HOSTPC1_PHCD, the_transceiver->regs + CI_HOSTPC1); +} + +/* Enable/Disable OTG interrupt */ +static void langwell_otg_intr(int on) +{ + u32 val; + + otg_dbg("interrupt -> %d\n", on); + + val = readl(the_transceiver->regs + CI_OTGSC); + if (on) { + val = val | (OTGSC_INTEN_MASK | OTGSC_IDPU); + writel(val, the_transceiver->regs + CI_OTGSC); + } else { + val = val & ~(OTGSC_INTEN_MASK | OTGSC_IDPU); + writel(val, the_transceiver->regs + CI_OTGSC); + } +} + +/* set HAAR: Hardware Assist Auto-Reset */ +static void langwell_otg_HAAR(int on) +{ + u32 val; + + otg_dbg("HAAR -> %d\n", on); + + val = readl(the_transceiver->regs + CI_OTGSC); + if (on) + writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_HAAR, + the_transceiver->regs + CI_OTGSC); + else + writel((val & ~OTGSC_INTSTS_MASK) & ~OTGSC_HAAR, + the_transceiver->regs + CI_OTGSC); +} + +/* set HABA: Hardware Assist B-Disconnect to A-Connect */ +static void langwell_otg_HABA(int on) +{ + u32 val; + + otg_dbg("HABA -> %d\n", on); + + val = readl(the_transceiver->regs + CI_OTGSC); + if (on) + writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_HABA, + the_transceiver->regs + CI_OTGSC); + else + writel((val & ~OTGSC_INTSTS_MASK) & ~OTGSC_HABA, + the_transceiver->regs + CI_OTGSC); +} + +static int langwell_otg_check_se0_srp(int on) +{ + u32 val; + + int delay_time = TB_SE0_SRP * 10; /* step is 100us */ + + otg_dbg("check_se0_srp -> \n"); + + do { + udelay(100); + if (!delay_time--) + break; + val = readl(the_transceiver->regs + CI_PORTSC1); + val &= PORTSC_LS; + } while (!val); + + otg_dbg("check_se0_srp <- \n"); + return val; +} + +/* The timeout callback function to set time out bit */ +static void set_tmout(unsigned long indicator) +{ + *(int *)indicator = 1; +} + +void langwell_otg_nsf_msg(unsigned long indicator) +{ + switch (indicator) { + case 2: + case 4: + case 6: + case 7: + printk(KERN_ERR "OTG:NSF-%lu - deivce not responding\n", + indicator); + break; + case 3: + printk(KERN_ERR "OTG:NSF-%lu - deivce not supported\n", + indicator); + break; + default: + printk(KERN_ERR "Do not have this kind of NSF\n"); + break; + } +} + +/* Initialize timers */ +static void langwell_otg_init_timers(struct otg_hsm *hsm) +{ + /* HSM used timers */ + a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE, + (unsigned long)&hsm->a_wait_vrise_tmout); + a_wait_bcon_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_BCON, + (unsigned long)&hsm->a_wait_bcon_tmout); + a_aidl_bdis_tmr = otg_timer_initializer(&set_tmout, TA_AIDL_BDIS, + (unsigned long)&hsm->a_aidl_bdis_tmout); + b_ase0_brst_tmr = otg_timer_initializer(&set_tmout, TB_ASE0_BRST, + (unsigned long)&hsm->b_ase0_brst_tmout); + b_se0_srp_tmr = otg_timer_initializer(&set_tmout, TB_SE0_SRP, + (unsigned long)&hsm->b_se0_srp); + b_srp_res_tmr = otg_timer_initializer(&set_tmout, TB_SRP_RES, + (unsigned long)&hsm->b_srp_res_tmout); + b_bus_suspend_tmr = otg_timer_initializer(&set_tmout, TB_BUS_SUSPEND, + (unsigned long)&hsm->b_bus_suspend_tmout); +} + +/* Free timers */ +static void langwell_otg_free_timers(void) +{ + kfree(a_wait_vrise_tmr); + kfree(a_wait_bcon_tmr); + kfree(a_aidl_bdis_tmr); + kfree(b_ase0_brst_tmr); + kfree(b_se0_srp_tmr); + kfree(b_srp_res_tmr); + kfree(b_bus_suspend_tmr); +} + +/* Add timer to timer list */ +static void langwell_otg_add_timer(void *gtimer) +{ + struct langwell_otg_timer *timer = (struct langwell_otg_timer *)gtimer; + struct langwell_otg_timer *tmp_timer; + u32 val32; + + /* Check if the timer is already in the active list, + * if so update timer count + */ + list_for_each_entry(tmp_timer, &active_timers, list) + if (tmp_timer == timer) { + timer->count = timer->expires; + return; + } + timer->count = timer->expires; + + if (list_empty(&active_timers)) { + val32 = readl(the_transceiver->regs + CI_OTGSC); + writel(val32 | OTGSC_1MSE, the_transceiver->regs + CI_OTGSC); + } + + list_add_tail(&timer->list, &active_timers); +} + +/* Remove timer from the timer list; clear timeout status */ +static void langwell_otg_del_timer(void *gtimer) +{ + struct langwell_otg_timer *timer = (struct langwell_otg_timer *)gtimer; + struct langwell_otg_timer *tmp_timer, *del_tmp; + u32 val32; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) + if (tmp_timer == timer) + list_del(&timer->list); + + if (list_empty(&active_timers)) { + val32 = readl(the_transceiver->regs + CI_OTGSC); + writel(val32 & ~OTGSC_1MSE, the_transceiver->regs + CI_OTGSC); + } +} + +/* Reduce timer count by 1, and find timeout conditions.*/ +static int langwell_otg_tick_timer(u32 *int_sts) +{ + struct langwell_otg_timer *tmp_timer, *del_tmp; + int expired = 0; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) { + tmp_timer->count--; + /* check if timer expires */ + if (!tmp_timer->count) { + list_del(&tmp_timer->list); + tmp_timer->function(tmp_timer->data); + expired = 1; + } + } + + if (list_empty(&active_timers)) { + otg_dbg("tick timer: disable 1ms int\n"); + *int_sts = *int_sts & ~OTGSC_1MSE; + } + return expired; +} + +static void reset_otg(void) +{ + u32 val; + int delay_time = 1000; + + otg_dbg("reseting OTG controller ...\n"); + val = readl(the_transceiver->regs + CI_USBCMD); + writel(val | USBCMD_RST, the_transceiver->regs + CI_USBCMD); + do { + udelay(100); + if (!delay_time--) + otg_dbg("reset timeout\n"); + val = readl(the_transceiver->regs + CI_USBCMD); + val &= USBCMD_RST; + } while (val != 0); + otg_dbg("reset done.\n"); +} + +static void set_host_mode(void) +{ + u32 val; + + reset_otg(); + val = readl(the_transceiver->regs + CI_USBMODE); + val = (val & (~USBMODE_CM)) | USBMODE_HOST; + writel(val, the_transceiver->regs + CI_USBMODE); +} + +static void set_client_mode(void) +{ + u32 val; + + reset_otg(); + val = readl(the_transceiver->regs + CI_USBMODE); + val = (val & (~USBMODE_CM)) | USBMODE_DEVICE; + writel(val, the_transceiver->regs + CI_USBMODE); +} + +static void init_hsm(void) +{ + struct langwell_otg *langwell = the_transceiver; + u32 val32; + + /* read OTGSC after reset */ + val32 = readl(langwell->regs + CI_OTGSC); + otg_dbg("%s: OTGSC init value = 0x%x\n", __func__, val32); + + /* set init state */ + if (val32 & OTGSC_ID) { + langwell->hsm.id = 1; + langwell->otg.default_a = 0; + set_client_mode(); + langwell->otg.state = OTG_STATE_B_IDLE; + langwell_otg_drv_vbus(0); + } else { + langwell->hsm.id = 0; + langwell->otg.default_a = 1; + set_host_mode(); + langwell->otg.state = OTG_STATE_A_IDLE; + } + + /* set session indicator */ + if (val32 & OTGSC_BSE) + langwell->hsm.b_sess_end = 1; + if (val32 & OTGSC_BSV) + langwell->hsm.b_sess_vld = 1; + if (val32 & OTGSC_ASV) + langwell->hsm.a_sess_vld = 1; + if (val32 & OTGSC_AVV) + langwell->hsm.a_vbus_vld = 1; + + /* defautly power the bus */ + langwell->hsm.a_bus_req = 1; + langwell->hsm.a_bus_drop = 0; + /* defautly don't request bus as B device */ + langwell->hsm.b_bus_req = 0; + /* no system error */ + langwell->hsm.a_clr_err = 0; +} + +static irqreturn_t otg_dummy_irq(int irq, void *_dev) +{ + void __iomem *reg_base = _dev; + u32 val; + u32 int_mask = 0; + + val = readl(reg_base + CI_USBMODE); + if ((val & USBMODE_CM) != USBMODE_DEVICE) + return IRQ_NONE; + + val = readl(reg_base + CI_USBSTS); + int_mask = val & INTR_DUMMY_MASK; + + if (int_mask == 0) + return IRQ_NONE; + + /* clear hsm.b_conn here since host driver can't detect it + * otg_dummy_irq called means B-disconnect happened. + */ + if (the_transceiver->hsm.b_conn) { + the_transceiver->hsm.b_conn = 0; + if (spin_trylock(&the_transceiver->wq_lock)) { + queue_work(the_transceiver->qwork, + &the_transceiver->work); + spin_unlock(&the_transceiver->wq_lock); + } + } + /* Clear interrupts */ + writel(int_mask, reg_base + CI_USBSTS); + return IRQ_HANDLED; +} + +static irqreturn_t otg_irq(int irq, void *_dev) +{ + struct langwell_otg *langwell = _dev; + u32 int_sts, int_en; + u32 int_mask = 0; + int flag = 0; + + int_sts = readl(langwell->regs + CI_OTGSC); + int_en = (int_sts & OTGSC_INTEN_MASK) >> 8; + int_mask = int_sts & int_en; + if (int_mask == 0) + return IRQ_NONE; + + if (int_mask & OTGSC_IDIS) { + otg_dbg("%s: id change int\n", __func__); + langwell->hsm.id = (int_sts & OTGSC_ID) ? 1 : 0; + flag = 1; + } + if (int_mask & OTGSC_DPIS) { + otg_dbg("%s: data pulse int\n", __func__); + langwell->hsm.a_srp_det = (int_sts & OTGSC_DPS) ? 1 : 0; + flag = 1; + } + if (int_mask & OTGSC_BSEIS) { + otg_dbg("%s: b session end int\n", __func__); + langwell->hsm.b_sess_end = (int_sts & OTGSC_BSE) ? 1 : 0; + flag = 1; + } + if (int_mask & OTGSC_BSVIS) { + otg_dbg("%s: b session valid int\n", __func__); + langwell->hsm.b_sess_vld = (int_sts & OTGSC_BSV) ? 1 : 0; + flag = 1; + } + if (int_mask & OTGSC_ASVIS) { + otg_dbg("%s: a session valid int\n", __func__); + langwell->hsm.a_sess_vld = (int_sts & OTGSC_ASV) ? 1 : 0; + flag = 1; + } + if (int_mask & OTGSC_AVVIS) { + otg_dbg("%s: a vbus valid int\n", __func__); + langwell->hsm.a_vbus_vld = (int_sts & OTGSC_AVV) ? 1 : 0; + flag = 1; + } + + if (int_mask & OTGSC_1MSS) { + /* need to schedule otg_work if any timer is expired */ + if (langwell_otg_tick_timer(&int_sts)) + flag = 1; + } + + writel((int_sts & ~OTGSC_INTSTS_MASK) | int_mask, + langwell->regs + CI_OTGSC); + if (flag) + queue_work(langwell->qwork, &langwell->work); + + return IRQ_HANDLED; +} + +static void langwell_otg_work(struct work_struct *work) +{ + struct langwell_otg *langwell = container_of(work, + struct langwell_otg, work); + int retval; + + otg_dbg("%s: old state = %s\n", __func__, + state_string(langwell->otg.state)); + + switch (langwell->otg.state) { + case OTG_STATE_UNDEFINED: + case OTG_STATE_B_IDLE: + if (!langwell->hsm.id) { + langwell_otg_del_timer(b_srp_res_tmr); + langwell->otg.default_a = 1; + langwell->hsm.a_srp_det = 0; + + langwell_otg_chrg_vbus(0); + langwell_otg_drv_vbus(0); + + set_host_mode(); + langwell->otg.state = OTG_STATE_A_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (langwell->hsm.b_srp_res_tmout) { + langwell->hsm.b_srp_res_tmout = 0; + langwell->hsm.b_bus_req = 0; + langwell_otg_nsf_msg(6); + } else if (langwell->hsm.b_sess_vld) { + langwell_otg_del_timer(b_srp_res_tmr); + langwell->hsm.b_sess_end = 0; + langwell->hsm.a_bus_suspend = 0; + + langwell_otg_chrg_vbus(0); + if (langwell->client_ops) { + langwell->client_ops->resume(langwell->pdev); + langwell->otg.state = OTG_STATE_B_PERIPHERAL; + } else + otg_dbg("client driver not loaded.\n"); + + } else if (langwell->hsm.b_bus_req && + (langwell->hsm.b_sess_end)) { + /* workaround for b_se0_srp detection */ + retval = langwell_otg_check_se0_srp(0); + if (retval) { + langwell->hsm.b_bus_req = 0; + otg_dbg("LS is not SE0, try again later\n"); + } else { + /* Start SRP */ + langwell_otg_start_srp(&langwell->otg); + langwell_otg_add_timer(b_srp_res_tmr); + } + } + break; + case OTG_STATE_B_SRP_INIT: + if (!langwell->hsm.id) { + langwell->otg.default_a = 1; + langwell->hsm.a_srp_det = 0; + + langwell_otg_drv_vbus(0); + langwell_otg_chrg_vbus(0); + + langwell->otg.state = OTG_STATE_A_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (langwell->hsm.b_sess_vld) { + langwell_otg_chrg_vbus(0); + if (langwell->client_ops) { + langwell->client_ops->resume(langwell->pdev); + langwell->otg.state = OTG_STATE_B_PERIPHERAL; + } else + otg_dbg("client driver not loaded.\n"); + } + break; + case OTG_STATE_B_PERIPHERAL: + if (!langwell->hsm.id) { + langwell->otg.default_a = 1; + langwell->hsm.a_srp_det = 0; + + langwell_otg_drv_vbus(0); + langwell_otg_chrg_vbus(0); + set_host_mode(); + + if (langwell->client_ops) { + langwell->client_ops->suspend(langwell->pdev, + PMSG_FREEZE); + } else + otg_dbg("client driver has been removed.\n"); + + langwell->otg.state = OTG_STATE_A_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (!langwell->hsm.b_sess_vld) { + langwell->hsm.b_hnp_enable = 0; + + if (langwell->client_ops) { + langwell->client_ops->suspend(langwell->pdev, + PMSG_FREEZE); + } else + otg_dbg("client driver has been removed.\n"); + + langwell->otg.state = OTG_STATE_B_IDLE; + } else if (langwell->hsm.b_bus_req && langwell->hsm.b_hnp_enable + && langwell->hsm.a_bus_suspend) { + + if (langwell->client_ops) { + langwell->client_ops->suspend(langwell->pdev, + PMSG_FREEZE); + } else + otg_dbg("client driver has been removed.\n"); + + langwell_otg_HAAR(1); + langwell->hsm.a_conn = 0; + + if (langwell->host_ops) { + langwell->host_ops->probe(langwell->pdev, + langwell->host_ops->id_table); + langwell->otg.state = OTG_STATE_B_WAIT_ACON; + } else + otg_dbg("host driver not loaded.\n"); + + langwell->hsm.a_bus_resume = 0; + langwell->hsm.b_ase0_brst_tmout = 0; + langwell_otg_add_timer(b_ase0_brst_tmr); + } + break; + + case OTG_STATE_B_WAIT_ACON: + if (!langwell->hsm.id) { + langwell_otg_del_timer(b_ase0_brst_tmr); + langwell->otg.default_a = 1; + langwell->hsm.a_srp_det = 0; + + langwell_otg_drv_vbus(0); + langwell_otg_chrg_vbus(0); + set_host_mode(); + + langwell_otg_HAAR(0); + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell->otg.state = OTG_STATE_A_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (!langwell->hsm.b_sess_vld) { + langwell_otg_del_timer(b_ase0_brst_tmr); + langwell->hsm.b_hnp_enable = 0; + langwell->hsm.b_bus_req = 0; + langwell_otg_chrg_vbus(0); + langwell_otg_HAAR(0); + + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell->otg.state = OTG_STATE_B_IDLE; + } else if (langwell->hsm.a_conn) { + langwell_otg_del_timer(b_ase0_brst_tmr); + langwell_otg_HAAR(0); + langwell->otg.state = OTG_STATE_B_HOST; + queue_work(langwell->qwork, &langwell->work); + } else if (langwell->hsm.a_bus_resume || + langwell->hsm.b_ase0_brst_tmout) { + langwell_otg_del_timer(b_ase0_brst_tmr); + langwell_otg_HAAR(0); + langwell_otg_nsf_msg(7); + + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + + langwell->hsm.a_bus_suspend = 0; + langwell->hsm.b_bus_req = 0; + + if (langwell->client_ops) + langwell->client_ops->resume(langwell->pdev); + else + otg_dbg("client driver not loaded.\n"); + + langwell->otg.state = OTG_STATE_B_PERIPHERAL; + } + break; + + case OTG_STATE_B_HOST: + if (!langwell->hsm.id) { + langwell->otg.default_a = 1; + langwell->hsm.a_srp_det = 0; + + langwell_otg_drv_vbus(0); + langwell_otg_chrg_vbus(0); + set_host_mode(); + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell->otg.state = OTG_STATE_A_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (!langwell->hsm.b_sess_vld) { + langwell->hsm.b_hnp_enable = 0; + langwell->hsm.b_bus_req = 0; + langwell_otg_chrg_vbus(0); + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell->otg.state = OTG_STATE_B_IDLE; + } else if ((!langwell->hsm.b_bus_req) || + (!langwell->hsm.a_conn)) { + langwell->hsm.b_bus_req = 0; + langwell_otg_loc_sof(0); + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + + langwell->hsm.a_bus_suspend = 0; + + if (langwell->client_ops) + langwell->client_ops->resume(langwell->pdev); + else + otg_dbg("client driver not loaded.\n"); + + langwell->otg.state = OTG_STATE_B_PERIPHERAL; + } + break; + + case OTG_STATE_A_IDLE: + langwell->otg.default_a = 1; + if (langwell->hsm.id) { + langwell->otg.default_a = 0; + langwell->hsm.b_bus_req = 0; + langwell_otg_drv_vbus(0); + langwell_otg_chrg_vbus(0); + + langwell->otg.state = OTG_STATE_B_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (langwell->hsm.a_sess_vld) { + langwell_otg_drv_vbus(1); + langwell->hsm.a_srp_det = 1; + langwell->hsm.a_wait_vrise_tmout = 0; + langwell_otg_add_timer(a_wait_vrise_tmr); + langwell->otg.state = OTG_STATE_A_WAIT_VRISE; + queue_work(langwell->qwork, &langwell->work); + } else if (!langwell->hsm.a_bus_drop && + (langwell->hsm.a_srp_det || langwell->hsm.a_bus_req)) { + langwell_otg_drv_vbus(1); + langwell->hsm.a_wait_vrise_tmout = 0; + langwell_otg_add_timer(a_wait_vrise_tmr); + langwell->otg.state = OTG_STATE_A_WAIT_VRISE; + queue_work(langwell->qwork, &langwell->work); + } + break; + case OTG_STATE_A_WAIT_VRISE: + if (langwell->hsm.id) { + langwell_otg_del_timer(a_wait_vrise_tmr); + langwell->hsm.b_bus_req = 0; + langwell->otg.default_a = 0; + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_B_IDLE; + } else if (langwell->hsm.a_vbus_vld) { + langwell_otg_del_timer(a_wait_vrise_tmr); + if (langwell->host_ops) + langwell->host_ops->probe(langwell->pdev, + langwell->host_ops->id_table); + else + otg_dbg("host driver not loaded.\n"); + langwell->hsm.b_conn = 0; + langwell->hsm.a_set_b_hnp_en = 0; + langwell->hsm.a_wait_bcon_tmout = 0; + langwell_otg_add_timer(a_wait_bcon_tmr); + langwell->otg.state = OTG_STATE_A_WAIT_BCON; + } else if (langwell->hsm.a_wait_vrise_tmout) { + if (langwell->hsm.a_vbus_vld) { + if (langwell->host_ops) + langwell->host_ops->probe( + langwell->pdev, + langwell->host_ops->id_table); + else + otg_dbg("host driver not loaded.\n"); + langwell->hsm.b_conn = 0; + langwell->hsm.a_set_b_hnp_en = 0; + langwell->hsm.a_wait_bcon_tmout = 0; + langwell_otg_add_timer(a_wait_bcon_tmr); + langwell->otg.state = OTG_STATE_A_WAIT_BCON; + } else { + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_VBUS_ERR; + } + } + break; + case OTG_STATE_A_WAIT_BCON: + if (langwell->hsm.id) { + langwell_otg_del_timer(a_wait_bcon_tmr); + + langwell->otg.default_a = 0; + langwell->hsm.b_bus_req = 0; + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_B_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (!langwell->hsm.a_vbus_vld) { + langwell_otg_del_timer(a_wait_bcon_tmr); + + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_VBUS_ERR; + } else if (langwell->hsm.a_bus_drop || + (langwell->hsm.a_wait_bcon_tmout && + !langwell->hsm.a_bus_req)) { + langwell_otg_del_timer(a_wait_bcon_tmr); + + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_WAIT_VFALL; + } else if (langwell->hsm.b_conn) { + langwell_otg_del_timer(a_wait_bcon_tmr); + + langwell->hsm.a_suspend_req = 0; + langwell->otg.state = OTG_STATE_A_HOST; + if (!langwell->hsm.a_bus_req && + langwell->hsm.a_set_b_hnp_en) { + /* It is not safe enough to do a fast + * transistion from A_WAIT_BCON to + * A_SUSPEND */ + msleep(10000); + if (langwell->hsm.a_bus_req) + break; + + if (request_irq(langwell->pdev->irq, + otg_dummy_irq, IRQF_SHARED, + driver_name, langwell->regs) != 0) { + otg_dbg("request interrupt %d fail\n", + langwell->pdev->irq); + } + + langwell_otg_HABA(1); + langwell->hsm.b_bus_resume = 0; + langwell->hsm.a_aidl_bdis_tmout = 0; + langwell_otg_add_timer(a_aidl_bdis_tmr); + + langwell_otg_loc_sof(0); + langwell->otg.state = OTG_STATE_A_SUSPEND; + } else if (!langwell->hsm.a_bus_req && + !langwell->hsm.a_set_b_hnp_en) { + struct pci_dev *pdev = langwell->pdev; + if (langwell->host_ops) + langwell->host_ops->remove(pdev); + else + otg_dbg("host driver removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_WAIT_VFALL; + } + } + break; + case OTG_STATE_A_HOST: + if (langwell->hsm.id) { + langwell->otg.default_a = 0; + langwell->hsm.b_bus_req = 0; + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_B_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (langwell->hsm.a_bus_drop || + (!langwell->hsm.a_set_b_hnp_en && !langwell->hsm.a_bus_req)) { + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_WAIT_VFALL; + } else if (!langwell->hsm.a_vbus_vld) { + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_VBUS_ERR; + } else if (langwell->hsm.a_set_b_hnp_en + && !langwell->hsm.a_bus_req) { + /* Set HABA to enable hardware assistance to signal + * A-connect after receiver B-disconnect. Hardware + * will then set client mode and enable URE, SLE and + * PCE after the assistance. otg_dummy_irq is used to + * clean these ints when client driver is not resumed. + */ + if (request_irq(langwell->pdev->irq, + otg_dummy_irq, IRQF_SHARED, driver_name, + langwell->regs) != 0) { + otg_dbg("request interrupt %d failed\n", + langwell->pdev->irq); + } + + /* set HABA */ + langwell_otg_HABA(1); + langwell->hsm.b_bus_resume = 0; + langwell->hsm.a_aidl_bdis_tmout = 0; + langwell_otg_add_timer(a_aidl_bdis_tmr); + langwell_otg_loc_sof(0); + langwell->otg.state = OTG_STATE_A_SUSPEND; + } else if (!langwell->hsm.b_conn || !langwell->hsm.a_bus_req) { + langwell->hsm.a_wait_bcon_tmout = 0; + langwell->hsm.a_set_b_hnp_en = 0; + langwell_otg_add_timer(a_wait_bcon_tmr); + langwell->otg.state = OTG_STATE_A_WAIT_BCON; + } + break; + case OTG_STATE_A_SUSPEND: + if (langwell->hsm.id) { + langwell_otg_del_timer(a_aidl_bdis_tmr); + langwell_otg_HABA(0); + free_irq(langwell->pdev->irq, langwell->regs); + langwell->otg.default_a = 0; + langwell->hsm.b_bus_req = 0; + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_B_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (langwell->hsm.a_bus_req || + langwell->hsm.b_bus_resume) { + langwell_otg_del_timer(a_aidl_bdis_tmr); + langwell_otg_HABA(0); + free_irq(langwell->pdev->irq, langwell->regs); + langwell->hsm.a_suspend_req = 0; + langwell_otg_loc_sof(1); + langwell->otg.state = OTG_STATE_A_HOST; + } else if (langwell->hsm.a_aidl_bdis_tmout || + langwell->hsm.a_bus_drop) { + langwell_otg_del_timer(a_aidl_bdis_tmr); + langwell_otg_HABA(0); + free_irq(langwell->pdev->irq, langwell->regs); + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_WAIT_VFALL; + } else if (!langwell->hsm.b_conn && + langwell->hsm.a_set_b_hnp_en) { + langwell_otg_del_timer(a_aidl_bdis_tmr); + langwell_otg_HABA(0); + free_irq(langwell->pdev->irq, langwell->regs); + + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + + langwell->hsm.b_bus_suspend = 0; + langwell->hsm.b_bus_suspend_vld = 0; + langwell->hsm.b_bus_suspend_tmout = 0; + + /* msleep(200); */ + if (langwell->client_ops) + langwell->client_ops->resume(langwell->pdev); + else + otg_dbg("client driver not loaded.\n"); + + langwell_otg_add_timer(b_bus_suspend_tmr); + langwell->otg.state = OTG_STATE_A_PERIPHERAL; + break; + } else if (!langwell->hsm.a_vbus_vld) { + langwell_otg_del_timer(a_aidl_bdis_tmr); + langwell_otg_HABA(0); + free_irq(langwell->pdev->irq, langwell->regs); + if (langwell->host_ops) + langwell->host_ops->remove(langwell->pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_VBUS_ERR; + } + break; + case OTG_STATE_A_PERIPHERAL: + if (langwell->hsm.id) { + langwell_otg_del_timer(b_bus_suspend_tmr); + langwell->otg.default_a = 0; + langwell->hsm.b_bus_req = 0; + if (langwell->client_ops) + langwell->client_ops->suspend(langwell->pdev, + PMSG_FREEZE); + else + otg_dbg("client driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_B_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (!langwell->hsm.a_vbus_vld) { + langwell_otg_del_timer(b_bus_suspend_tmr); + if (langwell->client_ops) + langwell->client_ops->suspend(langwell->pdev, + PMSG_FREEZE); + else + otg_dbg("client driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_VBUS_ERR; + } else if (langwell->hsm.a_bus_drop) { + langwell_otg_del_timer(b_bus_suspend_tmr); + if (langwell->client_ops) + langwell->client_ops->suspend(langwell->pdev, + PMSG_FREEZE); + else + otg_dbg("client driver has been removed.\n"); + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_WAIT_VFALL; + } else if (langwell->hsm.b_bus_suspend) { + langwell_otg_del_timer(b_bus_suspend_tmr); + if (langwell->client_ops) + langwell->client_ops->suspend(langwell->pdev, + PMSG_FREEZE); + else + otg_dbg("client driver has been removed.\n"); + + if (langwell->host_ops) + langwell->host_ops->probe(langwell->pdev, + langwell->host_ops->id_table); + else + otg_dbg("host driver not loaded.\n"); + langwell->hsm.a_set_b_hnp_en = 0; + langwell->hsm.a_wait_bcon_tmout = 0; + langwell_otg_add_timer(a_wait_bcon_tmr); + langwell->otg.state = OTG_STATE_A_WAIT_BCON; + } else if (langwell->hsm.b_bus_suspend_tmout) { + u32 val; + val = readl(langwell->regs + CI_PORTSC1); + if (!(val & PORTSC_SUSP)) + break; + if (langwell->client_ops) + langwell->client_ops->suspend(langwell->pdev, + PMSG_FREEZE); + else + otg_dbg("client driver has been removed.\n"); + if (langwell->host_ops) + langwell->host_ops->probe(langwell->pdev, + langwell->host_ops->id_table); + else + otg_dbg("host driver not loaded.\n"); + langwell->hsm.a_set_b_hnp_en = 0; + langwell->hsm.a_wait_bcon_tmout = 0; + langwell_otg_add_timer(a_wait_bcon_tmr); + langwell->otg.state = OTG_STATE_A_WAIT_BCON; + } + break; + case OTG_STATE_A_VBUS_ERR: + if (langwell->hsm.id) { + langwell->otg.default_a = 0; + langwell->hsm.a_clr_err = 0; + langwell->hsm.a_srp_det = 0; + langwell->otg.state = OTG_STATE_B_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (langwell->hsm.a_clr_err) { + langwell->hsm.a_clr_err = 0; + langwell->hsm.a_srp_det = 0; + reset_otg(); + init_hsm(); + if (langwell->otg.state == OTG_STATE_A_IDLE) + queue_work(langwell->qwork, &langwell->work); + } + break; + case OTG_STATE_A_WAIT_VFALL: + if (langwell->hsm.id) { + langwell->otg.default_a = 0; + langwell->otg.state = OTG_STATE_B_IDLE; + queue_work(langwell->qwork, &langwell->work); + } else if (langwell->hsm.a_bus_req) { + langwell_otg_drv_vbus(1); + langwell->hsm.a_wait_vrise_tmout = 0; + langwell_otg_add_timer(a_wait_vrise_tmr); + langwell->otg.state = OTG_STATE_A_WAIT_VRISE; + } else if (!langwell->hsm.a_sess_vld) { + langwell->hsm.a_srp_det = 0; + langwell_otg_drv_vbus(0); + set_host_mode(); + langwell->otg.state = OTG_STATE_A_IDLE; + } + break; + default: + ; + } + + otg_dbg("%s: new state = %s\n", __func__, + state_string(langwell->otg.state)); +} + + static ssize_t +show_registers(struct device *_dev, struct device_attribute *attr, char *buf) +{ + struct langwell_otg *langwell; + char *next; + unsigned size; + unsigned t; + + langwell = the_transceiver; + next = buf; + size = PAGE_SIZE; + + t = scnprintf(next, size, + "\n" + "USBCMD = 0x%08x \n" + "USBSTS = 0x%08x \n" + "USBINTR = 0x%08x \n" + "ASYNCLISTADDR = 0x%08x \n" + "PORTSC1 = 0x%08x \n" + "HOSTPC1 = 0x%08x \n" + "OTGSC = 0x%08x \n" + "USBMODE = 0x%08x \n", + readl(langwell->regs + 0x30), + readl(langwell->regs + 0x34), + readl(langwell->regs + 0x38), + readl(langwell->regs + 0x48), + readl(langwell->regs + 0x74), + readl(langwell->regs + 0xb4), + readl(langwell->regs + 0xf4), + readl(langwell->regs + 0xf8) + ); + size -= t; + next += t; + + return PAGE_SIZE - size; +} +static DEVICE_ATTR(registers, S_IRUGO, show_registers, NULL); + +static ssize_t +show_hsm(struct device *_dev, struct device_attribute *attr, char *buf) +{ + struct langwell_otg *langwell; + char *next; + unsigned size; + unsigned t; + + langwell = the_transceiver; + next = buf; + size = PAGE_SIZE; + + t = scnprintf(next, size, + "\n" + "current state = %s\n" + "a_bus_resume = \t%d\n" + "a_bus_suspend = \t%d\n" + "a_conn = \t%d\n" + "a_sess_vld = \t%d\n" + "a_srp_det = \t%d\n" + "a_vbus_vld = \t%d\n" + "b_bus_resume = \t%d\n" + "b_bus_suspend = \t%d\n" + "b_conn = \t%d\n" + "b_se0_srp = \t%d\n" + "b_sess_end = \t%d\n" + "b_sess_vld = \t%d\n" + "id = \t%d\n" + "a_set_b_hnp_en = \t%d\n" + "b_srp_done = \t%d\n" + "b_hnp_enable = \t%d\n" + "a_wait_vrise_tmout = \t%d\n" + "a_wait_bcon_tmout = \t%d\n" + "a_aidl_bdis_tmout = \t%d\n" + "b_ase0_brst_tmout = \t%d\n" + "a_bus_drop = \t%d\n" + "a_bus_req = \t%d\n" + "a_clr_err = \t%d\n" + "a_suspend_req = \t%d\n" + "b_bus_req = \t%d\n" + "b_bus_suspend_tmout = \t%d\n" + "b_bus_suspend_vld = \t%d\n", + state_string(langwell->otg.state), + langwell->hsm.a_bus_resume, + langwell->hsm.a_bus_suspend, + langwell->hsm.a_conn, + langwell->hsm.a_sess_vld, + langwell->hsm.a_srp_det, + langwell->hsm.a_vbus_vld, + langwell->hsm.b_bus_resume, + langwell->hsm.b_bus_suspend, + langwell->hsm.b_conn, + langwell->hsm.b_se0_srp, + langwell->hsm.b_sess_end, + langwell->hsm.b_sess_vld, + langwell->hsm.id, + langwell->hsm.a_set_b_hnp_en, + langwell->hsm.b_srp_done, + langwell->hsm.b_hnp_enable, + langwell->hsm.a_wait_vrise_tmout, + langwell->hsm.a_wait_bcon_tmout, + langwell->hsm.a_aidl_bdis_tmout, + langwell->hsm.b_ase0_brst_tmout, + langwell->hsm.a_bus_drop, + langwell->hsm.a_bus_req, + langwell->hsm.a_clr_err, + langwell->hsm.a_suspend_req, + langwell->hsm.b_bus_req, + langwell->hsm.b_bus_suspend_tmout, + langwell->hsm.b_bus_suspend_vld + ); + size -= t; + next += t; + + return PAGE_SIZE - size; +} +static DEVICE_ATTR(hsm, S_IRUGO, show_hsm, NULL); + +static ssize_t +get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct langwell_otg *langwell; + char *next; + unsigned size; + unsigned t; + + langwell = the_transceiver; + next = buf; + size = PAGE_SIZE; + + t = scnprintf(next, size, "%d", langwell->hsm.a_bus_req); + size -= t; + next += t; + + return PAGE_SIZE - size; +} + +static ssize_t +set_a_bus_req(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct langwell_otg *langwell; + langwell = the_transceiver; + if (!langwell->otg.default_a) + return -1; + if (count > 2) + return -1; + + if (buf[0] == '0') { + langwell->hsm.a_bus_req = 0; + otg_dbg("a_bus_req = 0\n"); + } else if (buf[0] == '1') { + /* If a_bus_drop is TRUE, a_bus_req can't be set */ + if (langwell->hsm.a_bus_drop) + return -1; + langwell->hsm.a_bus_req = 1; + otg_dbg("a_bus_req = 1\n"); + } + if (spin_trylock(&langwell->wq_lock)) { + queue_work(langwell->qwork, &langwell->work); + spin_unlock(&langwell->wq_lock); + } + return count; +} +static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUGO, get_a_bus_req, set_a_bus_req); + +static ssize_t +get_a_bus_drop(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct langwell_otg *langwell; + char *next; + unsigned size; + unsigned t; + + langwell = the_transceiver; + next = buf; + size = PAGE_SIZE; + + t = scnprintf(next, size, "%d", langwell->hsm.a_bus_drop); + size -= t; + next += t; + + return PAGE_SIZE - size; +} + +static ssize_t +set_a_bus_drop(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct langwell_otg *langwell; + langwell = the_transceiver; + if (!langwell->otg.default_a) + return -1; + if (count > 2) + return -1; + + if (buf[0] == '0') { + langwell->hsm.a_bus_drop = 0; + otg_dbg("a_bus_drop = 0\n"); + } else if (buf[0] == '1') { + langwell->hsm.a_bus_drop = 1; + langwell->hsm.a_bus_req = 0; + otg_dbg("a_bus_drop = 1, then a_bus_req = 0\n"); + } + if (spin_trylock(&langwell->wq_lock)) { + queue_work(langwell->qwork, &langwell->work); + spin_unlock(&langwell->wq_lock); + } + return count; +} +static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUGO, + get_a_bus_drop, set_a_bus_drop); + +static ssize_t +get_b_bus_req(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct langwell_otg *langwell; + char *next; + unsigned size; + unsigned t; + + langwell = the_transceiver; + next = buf; + size = PAGE_SIZE; + + t = scnprintf(next, size, "%d", langwell->hsm.b_bus_req); + size -= t; + next += t; + + return PAGE_SIZE - size; +} + +static ssize_t +set_b_bus_req(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct langwell_otg *langwell; + langwell = the_transceiver; + + if (langwell->otg.default_a) + return -1; + + if (count > 2) + return -1; + + if (buf[0] == '0') { + langwell->hsm.b_bus_req = 0; + otg_dbg("b_bus_req = 0\n"); + } else if (buf[0] == '1') { + langwell->hsm.b_bus_req = 1; + otg_dbg("b_bus_req = 1\n"); + } + if (spin_trylock(&langwell->wq_lock)) { + queue_work(langwell->qwork, &langwell->work); + spin_unlock(&langwell->wq_lock); + } + return count; +} +static DEVICE_ATTR(b_bus_req, S_IRUGO | S_IWUGO, get_b_bus_req, set_b_bus_req); + +static ssize_t +set_a_clr_err(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct langwell_otg *langwell; + langwell = the_transceiver; + + if (!langwell->otg.default_a) + return -1; + if (count > 2) + return -1; + + if (buf[0] == '1') { + langwell->hsm.a_clr_err = 1; + otg_dbg("a_clr_err = 1\n"); + } + if (spin_trylock(&langwell->wq_lock)) { + queue_work(langwell->qwork, &langwell->work); + spin_unlock(&langwell->wq_lock); + } + return count; +} +static DEVICE_ATTR(a_clr_err, S_IWUGO, NULL, set_a_clr_err); + +static struct attribute *inputs_attrs[] = { + &dev_attr_a_bus_req.attr, + &dev_attr_a_bus_drop.attr, + &dev_attr_b_bus_req.attr, + &dev_attr_a_clr_err.attr, + NULL, +}; + +static struct attribute_group debug_dev_attr_group = { + .name = "inputs", + .attrs = inputs_attrs, +}; + +int langwell_register_host(struct pci_driver *host_driver) +{ + int ret = 0; + + the_transceiver->host_ops = host_driver; + queue_work(the_transceiver->qwork, &the_transceiver->work); + otg_dbg("host controller driver is registered\n"); + + return ret; +} +EXPORT_SYMBOL(langwell_register_host); + +void langwell_unregister_host(struct pci_driver *host_driver) +{ + if (the_transceiver->host_ops) + the_transceiver->host_ops->remove(the_transceiver->pdev); + the_transceiver->host_ops = NULL; + the_transceiver->hsm.a_bus_drop = 1; + queue_work(the_transceiver->qwork, &the_transceiver->work); + otg_dbg("host controller driver is unregistered\n"); +} +EXPORT_SYMBOL(langwell_unregister_host); + +int langwell_register_peripheral(struct pci_driver *client_driver) +{ + int ret = 0; + + if (client_driver) + ret = client_driver->probe(the_transceiver->pdev, + client_driver->id_table); + if (!ret) { + the_transceiver->client_ops = client_driver; + queue_work(the_transceiver->qwork, &the_transceiver->work); + otg_dbg("client controller driver is registered\n"); + } + + return ret; +} +EXPORT_SYMBOL(langwell_register_peripheral); + +void langwell_unregister_peripheral(struct pci_driver *client_driver) +{ + if (the_transceiver->client_ops) + the_transceiver->client_ops->remove(the_transceiver->pdev); + the_transceiver->client_ops = NULL; + the_transceiver->hsm.b_bus_req = 0; + queue_work(the_transceiver->qwork, &the_transceiver->work); + otg_dbg("client controller driver is unregistered\n"); +} +EXPORT_SYMBOL(langwell_unregister_peripheral); + +static int langwell_otg_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + unsigned long resource, len; + void __iomem *base = NULL; + int retval; + u32 val32; + struct langwell_otg *langwell; + char qname[] = "langwell_otg_queue"; + + retval = 0; + otg_dbg("\notg controller is detected.\n"); + if (pci_enable_device(pdev) < 0) { + retval = -ENODEV; + goto done; + } + + langwell = kzalloc(sizeof *langwell, GFP_KERNEL); + if (langwell == NULL) { + retval = -ENOMEM; + goto done; + } + the_transceiver = langwell; + + /* control register: BAR 0 */ + resource = pci_resource_start(pdev, 0); + len = pci_resource_len(pdev, 0); + if (!request_mem_region(resource, len, driver_name)) { + retval = -EBUSY; + goto err; + } + langwell->region = 1; + + base = ioremap_nocache(resource, len); + if (base == NULL) { + retval = -EFAULT; + goto err; + } + langwell->regs = base; + + if (!pdev->irq) { + otg_dbg("No IRQ.\n"); + retval = -ENODEV; + goto err; + } + + langwell->qwork = create_workqueue(qname); + if (!langwell->qwork) { + otg_dbg("cannot create workqueue %s\n", qname); + retval = -ENOMEM; + goto err; + } + INIT_WORK(&langwell->work, langwell_otg_work); + + /* OTG common part */ + langwell->pdev = pdev; + langwell->otg.dev = &pdev->dev; + langwell->otg.label = driver_name; + langwell->otg.set_host = langwell_otg_set_host; + langwell->otg.set_peripheral = langwell_otg_set_peripheral; + langwell->otg.set_power = langwell_otg_set_power; + langwell->otg.start_srp = langwell_otg_start_srp; + langwell->otg.state = OTG_STATE_UNDEFINED; + if (otg_set_transceiver(&langwell->otg)) { + otg_dbg("can't set transceiver\n"); + retval = -EBUSY; + goto err; + } + + reset_otg(); + init_hsm(); + + spin_lock_init(&langwell->lock); + spin_lock_init(&langwell->wq_lock); + INIT_LIST_HEAD(&active_timers); + langwell_otg_init_timers(&langwell->hsm); + + if (request_irq(pdev->irq, otg_irq, IRQF_SHARED, + driver_name, langwell) != 0) { + otg_dbg("request interrupt %d failed\n", pdev->irq); + retval = -EBUSY; + goto err; + } + + /* enable OTGSC int */ + val32 = OTGSC_DPIE | OTGSC_BSEIE | OTGSC_BSVIE | + OTGSC_ASVIE | OTGSC_AVVIE | OTGSC_IDIE | OTGSC_IDPU; + writel(val32, langwell->regs + CI_OTGSC); + + retval = device_create_file(&pdev->dev, &dev_attr_registers); + if (retval < 0) { + otg_dbg("Can't register sysfs attribute: %d\n", retval); + goto err; + } + + retval = device_create_file(&pdev->dev, &dev_attr_hsm); + if (retval < 0) { + otg_dbg("Can't hsm sysfs attribute: %d\n", retval); + goto err; + } + + retval = sysfs_create_group(&pdev->dev.kobj, &debug_dev_attr_group); + if (retval < 0) { + otg_dbg("Can't register sysfs attr group: %d\n", retval); + goto err; + } + + if (langwell->otg.state == OTG_STATE_A_IDLE) + queue_work(langwell->qwork, &langwell->work); + + return 0; + +err: + if (the_transceiver) + langwell_otg_remove(pdev); +done: + return retval; +} + +static void langwell_otg_remove(struct pci_dev *pdev) +{ + struct langwell_otg *langwell; + + langwell = the_transceiver; + + if (langwell->qwork) { + flush_workqueue(langwell->qwork); + destroy_workqueue(langwell->qwork); + } + langwell_otg_free_timers(); + + /* disable OTGSC interrupt as OTGSC doesn't change in reset */ + writel(0, langwell->regs + CI_OTGSC); + + if (pdev->irq) + free_irq(pdev->irq, langwell); + if (langwell->regs) + iounmap(langwell->regs); + if (langwell->region) + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + + otg_set_transceiver(NULL); + pci_disable_device(pdev); + sysfs_remove_group(&pdev->dev.kobj, &debug_dev_attr_group); + device_remove_file(&pdev->dev, &dev_attr_hsm); + device_remove_file(&pdev->dev, &dev_attr_registers); + kfree(langwell); + langwell = NULL; +} + +static void transceiver_suspend(struct pci_dev *pdev) +{ + pci_save_state(pdev); + pci_set_power_state(pdev, PCI_D3hot); + langwell_otg_phy_low_power(1); +} + +static int langwell_otg_suspend(struct pci_dev *pdev, pm_message_t message) +{ + int ret = 0; + struct langwell_otg *langwell; + + langwell = the_transceiver; + + /* Disbale OTG interrupts */ + langwell_otg_intr(0); + + if (pdev->irq) + free_irq(pdev->irq, langwell); + + /* Prevent more otg_work */ + flush_workqueue(langwell->qwork); + spin_lock(&langwell->wq_lock); + + /* start actions */ + switch (langwell->otg.state) { + case OTG_STATE_A_IDLE: + case OTG_STATE_B_IDLE: + case OTG_STATE_A_WAIT_VFALL: + case OTG_STATE_A_VBUS_ERR: + transceiver_suspend(pdev); + break; + case OTG_STATE_A_WAIT_VRISE: + langwell_otg_del_timer(a_wait_vrise_tmr); + langwell->hsm.a_srp_det = 0; + langwell_otg_drv_vbus(0); + langwell->otg.state = OTG_STATE_A_IDLE; + transceiver_suspend(pdev); + break; + case OTG_STATE_A_WAIT_BCON: + langwell_otg_del_timer(a_wait_bcon_tmr); + if (langwell->host_ops) + ret = langwell->host_ops->suspend(pdev, message); + langwell_otg_drv_vbus(0); + break; + case OTG_STATE_A_HOST: + if (langwell->host_ops) + ret = langwell->host_ops->suspend(pdev, message); + langwell_otg_drv_vbus(0); + langwell_otg_phy_low_power(1); + break; + case OTG_STATE_A_SUSPEND: + langwell_otg_del_timer(a_aidl_bdis_tmr); + langwell_otg_HABA(0); + if (langwell->host_ops) + langwell->host_ops->remove(pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell_otg_drv_vbus(0); + transceiver_suspend(pdev); + langwell->otg.state = OTG_STATE_A_WAIT_VFALL; + break; + case OTG_STATE_A_PERIPHERAL: + if (langwell->client_ops) + ret = langwell->client_ops->suspend(pdev, message); + else + otg_dbg("client driver has been removed.\n"); + langwell_otg_drv_vbus(0); + transceiver_suspend(pdev); + langwell->otg.state = OTG_STATE_A_WAIT_VFALL; + break; + case OTG_STATE_B_HOST: + if (langwell->host_ops) + langwell->host_ops->remove(pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell->hsm.b_bus_req = 0; + transceiver_suspend(pdev); + langwell->otg.state = OTG_STATE_B_IDLE; + break; + case OTG_STATE_B_PERIPHERAL: + if (langwell->client_ops) + ret = langwell->client_ops->suspend(pdev, message); + else + otg_dbg("client driver has been removed.\n"); + break; + case OTG_STATE_B_WAIT_ACON: + langwell_otg_del_timer(b_ase0_brst_tmr); + langwell_otg_HAAR(0); + if (langwell->host_ops) + langwell->host_ops->remove(pdev); + else + otg_dbg("host driver has been removed.\n"); + langwell->hsm.b_bus_req = 0; + langwell->otg.state = OTG_STATE_B_IDLE; + transceiver_suspend(pdev); + break; + default: + otg_dbg("error state before suspend\n "); + break; + } + spin_unlock(&langwell->wq_lock); + + return ret; +} + +static void transceiver_resume(struct pci_dev *pdev) +{ + pci_restore_state(pdev); + pci_set_power_state(pdev, PCI_D0); + langwell_otg_phy_low_power(0); +} + +static int langwell_otg_resume(struct pci_dev *pdev) +{ + int ret = 0; + struct langwell_otg *langwell; + + langwell = the_transceiver; + + spin_lock(&langwell->wq_lock); + + switch (langwell->otg.state) { + case OTG_STATE_A_IDLE: + case OTG_STATE_B_IDLE: + case OTG_STATE_A_WAIT_VFALL: + case OTG_STATE_A_VBUS_ERR: + transceiver_resume(pdev); + break; + case OTG_STATE_A_WAIT_BCON: + langwell_otg_add_timer(a_wait_bcon_tmr); + langwell_otg_drv_vbus(1); + if (langwell->host_ops) + ret = langwell->host_ops->resume(pdev); + break; + case OTG_STATE_A_HOST: + langwell_otg_drv_vbus(1); + langwell_otg_phy_low_power(0); + if (langwell->host_ops) + ret = langwell->host_ops->resume(pdev); + break; + case OTG_STATE_B_PERIPHERAL: + if (langwell->client_ops) + ret = langwell->client_ops->resume(pdev); + else + otg_dbg("client driver not loaded.\n"); + break; + default: + otg_dbg("error state before suspend\n "); + break; + } + + if (request_irq(pdev->irq, otg_irq, IRQF_SHARED, + driver_name, the_transceiver) != 0) { + otg_dbg("request interrupt %d failed\n", pdev->irq); + ret = -EBUSY; + } + + /* enable OTG interrupts */ + langwell_otg_intr(1); + + spin_unlock(&langwell->wq_lock); + + queue_work(langwell->qwork, &langwell->work); + + + return ret; +} + +static int __init langwell_otg_init(void) +{ + return pci_register_driver(&otg_pci_driver); +} +module_init(langwell_otg_init); + +static void __exit langwell_otg_cleanup(void) +{ + pci_unregister_driver(&otg_pci_driver); +} +module_exit(langwell_otg_cleanup); diff --git a/include/linux/usb/langwell_otg.h b/include/linux/usb/langwell_otg.h new file mode 100644 index 000000000000..e115ae6df1da --- /dev/null +++ b/include/linux/usb/langwell_otg.h @@ -0,0 +1,177 @@ +/* + * Intel Langwell USB OTG transceiver driver + * Copyright (C) 2008, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef __LANGWELL_OTG_H__ +#define __LANGWELL_OTG_H__ + +/* notify transceiver driver about OTG events */ +extern void langwell_update_transceiver(void); +/* HCD register bus driver */ +extern int langwell_register_host(struct pci_driver *host_driver); +/* HCD unregister bus driver */ +extern void langwell_unregister_host(struct pci_driver *host_driver); +/* DCD register bus driver */ +extern int langwell_register_peripheral(struct pci_driver *client_driver); +/* DCD unregister bus driver */ +extern void langwell_unregister_peripheral(struct pci_driver *client_driver); +/* No silent failure, output warning message */ +extern void langwell_otg_nsf_msg(unsigned long message); + +#define CI_USBCMD 0x30 +# define USBCMD_RST BIT(1) +# define USBCMD_RS BIT(0) +#define CI_USBSTS 0x34 +# define USBSTS_SLI BIT(8) +# define USBSTS_URI BIT(6) +# define USBSTS_PCI BIT(2) +#define CI_PORTSC1 0x74 +# define PORTSC_PP BIT(12) +# define PORTSC_LS (BIT(11) | BIT(10)) +# define PORTSC_SUSP BIT(7) +# define PORTSC_CCS BIT(0) +#define CI_HOSTPC1 0xb4 +# define HOSTPC1_PHCD BIT(22) +#define CI_OTGSC 0xf4 +# define OTGSC_DPIE BIT(30) +# define OTGSC_1MSE BIT(29) +# define OTGSC_BSEIE BIT(28) +# define OTGSC_BSVIE BIT(27) +# define OTGSC_ASVIE BIT(26) +# define OTGSC_AVVIE BIT(25) +# define OTGSC_IDIE BIT(24) +# define OTGSC_DPIS BIT(22) +# define OTGSC_1MSS BIT(21) +# define OTGSC_BSEIS BIT(20) +# define OTGSC_BSVIS BIT(19) +# define OTGSC_ASVIS BIT(18) +# define OTGSC_AVVIS BIT(17) +# define OTGSC_IDIS BIT(16) +# define OTGSC_DPS BIT(14) +# define OTGSC_1MST BIT(13) +# define OTGSC_BSE BIT(12) +# define OTGSC_BSV BIT(11) +# define OTGSC_ASV BIT(10) +# define OTGSC_AVV BIT(9) +# define OTGSC_ID BIT(8) +# define OTGSC_HABA BIT(7) +# define OTGSC_HADP BIT(6) +# define OTGSC_IDPU BIT(5) +# define OTGSC_DP BIT(4) +# define OTGSC_OT BIT(3) +# define OTGSC_HAAR BIT(2) +# define OTGSC_VC BIT(1) +# define OTGSC_VD BIT(0) +# define OTGSC_INTEN_MASK (0x7f << 24) +# define OTGSC_INTSTS_MASK (0x7f << 16) +#define CI_USBMODE 0xf8 +# define USBMODE_CM (BIT(1) | BIT(0)) +# define USBMODE_IDLE 0 +# define USBMODE_DEVICE 0x2 +# define USBMODE_HOST 0x3 + +#define INTR_DUMMY_MASK (USBSTS_SLI | USBSTS_URI | USBSTS_PCI) + +struct otg_hsm { + /* Input */ + int a_bus_resume; + int a_bus_suspend; + int a_conn; + int a_sess_vld; + int a_srp_det; + int a_vbus_vld; + int b_bus_resume; + int b_bus_suspend; + int b_conn; + int b_se0_srp; + int b_sess_end; + int b_sess_vld; + int id; + + /* Internal variables */ + int a_set_b_hnp_en; + int b_srp_done; + int b_hnp_enable; + + /* Timeout indicator for timers */ + int a_wait_vrise_tmout; + int a_wait_bcon_tmout; + int a_aidl_bdis_tmout; + int b_ase0_brst_tmout; + int b_bus_suspend_tmout; + int b_srp_res_tmout; + + /* Informative variables */ + int a_bus_drop; + int a_bus_req; + int a_clr_err; + int a_suspend_req; + int b_bus_req; + + /* Output */ + int drv_vbus; + int loc_conn; + int loc_sof; + + /* Others */ + int b_bus_suspend_vld; +}; + +#define TA_WAIT_VRISE 100 +#define TA_WAIT_BCON 30000 +#define TA_AIDL_BDIS 15000 +#define TB_ASE0_BRST 5000 +#define TB_SE0_SRP 2 +#define TB_SRP_RES 100 +#define TB_BUS_SUSPEND 500 + +struct langwell_otg_timer { + unsigned long expires; /* Number of count increase to timeout */ + unsigned long count; /* Tick counter */ + void (*function)(unsigned long); /* Timeout function */ + unsigned long data; /* Data passed to function */ + struct list_head list; +}; + +struct langwell_otg { + struct otg_transceiver otg; + struct otg_hsm hsm; + void __iomem *regs; + unsigned region; + struct pci_driver *host_ops; + struct pci_driver *client_ops; + struct pci_dev *pdev; + struct work_struct work; + struct workqueue_struct *qwork; + spinlock_t lock; + spinlock_t wq_lock; +}; + +static inline struct langwell_otg *otg_to_langwell(struct otg_transceiver *otg) +{ + return container_of(otg, struct langwell_otg, otg); +} + +#ifdef DEBUG +#define otg_dbg(fmt, args...) \ + printk(KERN_DEBUG fmt , ## args) +#else +#define otg_dbg(fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ +#endif /* __LANGWELL_OTG_H__ */ -- cgit v1.2.3 From 23a54e567534d4895056df558e2564114513524a Mon Sep 17 00:00:00 2001 From: Frans Pop <elendil@planet.nl> Date: Thu, 4 Jun 2009 22:30:48 +0200 Subject: USB: Avoid PM error messages during resume if a device was disconnected Currently if a laptop is suspended e.g. while docked and then resumed after undocking it, the following errors get generated because the USB hub in the docking station and the devices connected to it are no longer available: pm_op(): usb_dev_resume+0x0/0x10 returns -19 PM: Device 1-2 failed to resume: error -19 pm_op(): usb_dev_resume+0x0/0x10 returns -19 PM: Device 1-2.2 failed to resume: error -19 pm_op(): usb_dev_resume+0x0/0x10 returns -19 PM: Device 1-2.3 failed to resume: error -19 As the removal of USB devices while a system is suspended is a relatively common use case and in most cases not an error, just return success on -ENODEV. The user gets informed anyway as the USB subsystem generates regular disconnect messages for the devices shortly afterwards: usb 1-2: USB disconnect, address 3 usb 1-2.2: USB disconnect, address 4 usblp0: removed usb 1-2.3: USB disconnect, address 5 Signed-off-by: Frans Pop <elendil@planet.nl> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/driver.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 77de8d65862c..69e5773abfce 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1750,6 +1750,7 @@ int usb_suspend(struct device *dev, pm_message_t msg) int usb_resume(struct device *dev, pm_message_t msg) { struct usb_device *udev; + int status; udev = to_usb_device(dev); @@ -1759,7 +1760,14 @@ int usb_resume(struct device *dev, pm_message_t msg) */ if (udev->skip_sys_resume) return 0; - return usb_external_resume_device(udev, msg); + status = usb_external_resume_device(udev, msg); + + /* Avoid PM error messages for devices disconnected while suspended + * as we'll display regular disconnect messages just a bit later. + */ + if (status == -ENODEV) + return 0; + return status; } #endif /* CONFIG_PM */ -- cgit v1.2.3 From e60c65d35951ef3527596442338c371ea16d55ed Mon Sep 17 00:00:00 2001 From: Nicolas Ferre <nicolas.ferre@atmel.com> Date: Fri, 5 Jun 2009 17:46:16 +0200 Subject: USB: atmel_usba_udc: change way of specifying bias function The toggle_bias() function was specified differently for avr32 and at91 architectures. Now, new at91 have the same behavior as avr32. Consequently, we change to a particular chip function definition: only for at91sam9rl. Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> Acked-by: Haavard Skinnemoen <haavard.skinnemoen@atmel.com> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/gadget/atmel_usba_udc.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c index 7a8f4421924c..4e970cf0e29a 100644 --- a/drivers/usb/gadget/atmel_usba_udc.c +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -326,13 +326,7 @@ static int vbus_is_present(struct usba_udc *udc) return 1; } -#if defined(CONFIG_AVR32) - -static void toggle_bias(int is_on) -{ -} - -#elif defined(CONFIG_ARCH_AT91) +#if defined(CONFIG_ARCH_AT91SAM9RL) #include <mach/at91_pmc.h> @@ -346,7 +340,13 @@ static void toggle_bias(int is_on) at91_sys_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); } -#endif /* CONFIG_ARCH_AT91 */ +#else + +static void toggle_bias(int is_on) +{ +} + +#endif /* CONFIG_ARCH_AT91SAM9RL */ static void next_fifo_transaction(struct usba_ep *ep, struct usba_request *req) { -- cgit v1.2.3 From 3b6c023f831e3179c42bbcff189c73628cd5ea4c Mon Sep 17 00:00:00 2001 From: Martin Fuzzey <mfuzzey@gmail.com> Date: Thu, 4 Jun 2009 23:20:38 +0200 Subject: USB: usbtest fix endless loop in unlink tests. In tests 11 and 12 if the URB completes with an error status (eg babble) the asynchrous unlink entered an endless loop trying to unlink a non resubmitted URB. Signed-off-by: Martin Fuzzey <mfuzzey@gmail.com> Acked-by: David Brownell <david-b@pacbell.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/misc/usbtest.c | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 5f1a19d1497d..a9f06d76960f 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -1072,23 +1072,34 @@ static int unlink1 (struct usbtest_dev *dev, int pipe, int size, int async) */ msleep (jiffies % (2 * INTERRUPT_RATE)); if (async) { -retry: - retval = usb_unlink_urb (urb); - if (retval == -EBUSY || retval == -EIDRM) { - /* we can't unlink urbs while they're completing. - * or if they've completed, and we haven't resubmitted. - * "normal" drivers would prevent resubmission, but - * since we're testing unlink paths, we can't. - */ - ERROR(dev, "unlink retry\n"); - goto retry; + while (!completion_done(&completion)) { + retval = usb_unlink_urb(urb); + + switch (retval) { + case -EBUSY: + case -EIDRM: + /* we can't unlink urbs while they're completing + * or if they've completed, and we haven't + * resubmitted. "normal" drivers would prevent + * resubmission, but since we're testing unlink + * paths, we can't. + */ + ERROR(dev, "unlink retry\n"); + continue; + case 0: + case -EINPROGRESS: + break; + + default: + dev_err(&dev->intf->dev, + "unlink fail %d\n", retval); + return retval; + } + + break; } } else usb_kill_urb (urb); - if (!(retval == 0 || retval == -EINPROGRESS)) { - dev_err(&dev->intf->dev, "unlink fail %d\n", retval); - return retval; - } wait_for_completion (&completion); retval = urb->status; -- cgit v1.2.3 From a92b63e7e4c185b4dd9e87762e2cb716e54482d0 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Mon, 15 Jun 2009 13:13:05 -0700 Subject: USB: usbtmc: fix switch statment Steve Holland pointed out that we forgot to call break; in the switch statment. This probably resolves a lot of the bug reports I've gotten for the driver lately. Stupid me... Reported-by: Steve Holland <sdh4@iastate.edu> Cc: stable <stable@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/class/usbtmc.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index c40a9b284cc9..3703789d0d2a 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -927,21 +927,27 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) switch (cmd) { case USBTMC_IOCTL_CLEAR_OUT_HALT: retval = usbtmc_ioctl_clear_out_halt(data); + break; case USBTMC_IOCTL_CLEAR_IN_HALT: retval = usbtmc_ioctl_clear_in_halt(data); + break; case USBTMC_IOCTL_INDICATOR_PULSE: retval = usbtmc_ioctl_indicator_pulse(data); + break; case USBTMC_IOCTL_CLEAR: retval = usbtmc_ioctl_clear(data); + break; case USBTMC_IOCTL_ABORT_BULK_OUT: retval = usbtmc_ioctl_abort_bulk_out(data); + break; case USBTMC_IOCTL_ABORT_BULK_IN: retval = usbtmc_ioctl_abort_bulk_in(data); + break; } mutex_unlock(&data->io_mutex); -- cgit v1.2.3 From 74c6874199af98e602bb7c5fb1beb9cffda98729 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:52:22 -0700 Subject: USB: xhci: Support xHCI host controllers and USB 3.0 devices. This is the first of many patches to add support for USB 3.0 devices and the hardware that implements the eXtensible Host Controller Interface (xHCI) 0.95 specification. This specification is not yet publicly available, but companies can receive a copy by becoming an xHCI Contributor (see http://www.intel.com/technology/usb/xhcispec.htm). No xHCI hardware has made it onto the market yet, but these patches have been tested under the Fresco Logic host controller prototype. This patch adds the xHCI register sets, which are grouped into five sets: - Generic PCI registers - Host controller "capabilities" registers (cap_regs) short - Host controller "operational" registers (op_regs) - Host controller "runtime" registers (run_regs) - Host controller "doorbell" registers These some of these registers may be virtualized if the Linux driver is running under a VM. Virtualization has not been tested for this patch. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-dbg.c | 229 ++++++++++++++++++++ drivers/usb/host/xhci-ext-caps.h | 145 +++++++++++++ drivers/usb/host/xhci.h | 452 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 826 insertions(+) create mode 100644 drivers/usb/host/xhci-dbg.c create mode 100644 drivers/usb/host/xhci-ext-caps.h create mode 100644 drivers/usb/host/xhci.h (limited to 'drivers') diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c new file mode 100644 index 000000000000..a7798b460492 --- /dev/null +++ b/drivers/usb/host/xhci-dbg.c @@ -0,0 +1,229 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "xhci.h" + +#define XHCI_INIT_VALUE 0x0 + +/* Add verbose debugging later, just print everything for now */ + +void xhci_dbg_regs(struct xhci_hcd *xhci) +{ + u32 temp; + + xhci_dbg(xhci, "// xHCI capability registers at 0x%x:\n", + (unsigned int) xhci->cap_regs); + temp = xhci_readl(xhci, &xhci->cap_regs->hc_capbase); + xhci_dbg(xhci, "// @%x = 0x%x (CAPLENGTH AND HCIVERSION)\n", + (unsigned int) &xhci->cap_regs->hc_capbase, + (unsigned int) temp); + xhci_dbg(xhci, "// CAPLENGTH: 0x%x\n", + (unsigned int) HC_LENGTH(temp)); +#if 0 + xhci_dbg(xhci, "// HCIVERSION: 0x%x\n", + (unsigned int) HC_VERSION(temp)); +#endif + + xhci_dbg(xhci, "// xHCI operational registers at 0x%x:\n", + (unsigned int) xhci->op_regs); + + temp = xhci_readl(xhci, &xhci->cap_regs->run_regs_off); + xhci_dbg(xhci, "// @%x = 0x%x RTSOFF\n", + (unsigned int) &xhci->cap_regs->run_regs_off, + (unsigned int) temp & RTSOFF_MASK); + xhci_dbg(xhci, "// xHCI runtime registers at 0x%x:\n", + (unsigned int) xhci->run_regs); + + temp = xhci_readl(xhci, &xhci->cap_regs->db_off); + xhci_dbg(xhci, "// @%x = 0x%x DBOFF\n", + (unsigned int) &xhci->cap_regs->db_off, temp); +} + +void xhci_print_cap_regs(struct xhci_hcd *xhci) +{ + u32 temp; + + xhci_dbg(xhci, "xHCI capability registers at 0x%x:\n", + (unsigned int) xhci->cap_regs); + + temp = xhci_readl(xhci, &xhci->cap_regs->hc_capbase); + xhci_dbg(xhci, "CAPLENGTH AND HCIVERSION 0x%x:\n", + (unsigned int) temp); + xhci_dbg(xhci, "CAPLENGTH: 0x%x\n", + (unsigned int) HC_LENGTH(temp)); + xhci_dbg(xhci, "HCIVERSION: 0x%x\n", + (unsigned int) HC_VERSION(temp)); + + temp = xhci_readl(xhci, &xhci->cap_regs->hcs_params1); + xhci_dbg(xhci, "HCSPARAMS 1: 0x%x\n", + (unsigned int) temp); + xhci_dbg(xhci, " Max device slots: %u\n", + (unsigned int) HCS_MAX_SLOTS(temp)); + xhci_dbg(xhci, " Max interrupters: %u\n", + (unsigned int) HCS_MAX_INTRS(temp)); + xhci_dbg(xhci, " Max ports: %u\n", + (unsigned int) HCS_MAX_PORTS(temp)); + + temp = xhci_readl(xhci, &xhci->cap_regs->hcs_params2); + xhci_dbg(xhci, "HCSPARAMS 2: 0x%x\n", + (unsigned int) temp); + xhci_dbg(xhci, " Isoc scheduling threshold: %u\n", + (unsigned int) HCS_IST(temp)); + xhci_dbg(xhci, " Maximum allowed segments in event ring: %u\n", + (unsigned int) HCS_ERST_MAX(temp)); + + temp = xhci_readl(xhci, &xhci->cap_regs->hcs_params3); + xhci_dbg(xhci, "HCSPARAMS 3 0x%x:\n", + (unsigned int) temp); + xhci_dbg(xhci, " Worst case U1 device exit latency: %u\n", + (unsigned int) HCS_U1_LATENCY(temp)); + xhci_dbg(xhci, " Worst case U2 device exit latency: %u\n", + (unsigned int) HCS_U2_LATENCY(temp)); + + temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params); + xhci_dbg(xhci, "HCC PARAMS 0x%x:\n", (unsigned int) temp); + xhci_dbg(xhci, " HC generates %s bit addresses\n", + HCC_64BIT_ADDR(temp) ? "64" : "32"); + /* FIXME */ + xhci_dbg(xhci, " FIXME: more HCCPARAMS debugging\n"); + + temp = xhci_readl(xhci, &xhci->cap_regs->run_regs_off); + xhci_dbg(xhci, "RTSOFF 0x%x:\n", temp & RTSOFF_MASK); +} + +void xhci_print_command_reg(struct xhci_hcd *xhci) +{ + u32 temp; + + temp = xhci_readl(xhci, &xhci->op_regs->command); + xhci_dbg(xhci, "USBCMD 0x%x:\n", temp); + xhci_dbg(xhci, " HC is %s\n", + (temp & CMD_RUN) ? "running" : "being stopped"); + xhci_dbg(xhci, " HC has %sfinished hard reset\n", + (temp & CMD_RESET) ? "not " : ""); + xhci_dbg(xhci, " Event Interrupts %s\n", + (temp & CMD_EIE) ? "enabled " : "disabled"); + xhci_dbg(xhci, " Host System Error Interrupts %s\n", + (temp & CMD_EIE) ? "enabled " : "disabled"); + xhci_dbg(xhci, " HC has %sfinished light reset\n", + (temp & CMD_LRESET) ? "not " : ""); +} + +void xhci_print_status(struct xhci_hcd *xhci) +{ + u32 temp; + + temp = xhci_readl(xhci, &xhci->op_regs->status); + xhci_dbg(xhci, "USBSTS 0x%x:\n", temp); + xhci_dbg(xhci, " Event ring is %sempty\n", + (temp & STS_EINT) ? "not " : ""); + xhci_dbg(xhci, " %sHost System Error\n", + (temp & STS_FATAL) ? "WARNING: " : "No "); + xhci_dbg(xhci, " HC is %s\n", + (temp & STS_HALT) ? "halted" : "running"); +} + +void xhci_print_op_regs(struct xhci_hcd *xhci) +{ + xhci_dbg(xhci, "xHCI operational registers at 0x%x:\n", + (unsigned int) xhci->op_regs); + xhci_print_command_reg(xhci); + xhci_print_status(xhci); +} + +void xhci_print_ir_set(struct xhci_hcd *xhci, struct intr_reg *ir_set, int set_num) +{ + void *addr; + u32 temp; + + addr = &ir_set->irq_pending; + temp = xhci_readl(xhci, addr); + if (temp == XHCI_INIT_VALUE) + return; + + xhci_dbg(xhci, " 0x%x: ir_set[%i]\n", (unsigned int) ir_set, set_num); + + xhci_dbg(xhci, " 0x%x: ir_set.pending = 0x%x\n", + (unsigned int) addr, (unsigned int) temp); + + addr = &ir_set->irq_control; + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, " 0x%x: ir_set.control = 0x%x\n", + (unsigned int) addr, (unsigned int) temp); + + addr = &ir_set->erst_size; + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, " 0x%x: ir_set.erst_size = 0x%x\n", + (unsigned int) addr, (unsigned int) temp); + + addr = &ir_set->rsvd; + temp = xhci_readl(xhci, addr); + if (temp != XHCI_INIT_VALUE) + xhci_dbg(xhci, " WARN: 0x%x: ir_set.rsvd = 0x%x\n", + (unsigned int) addr, (unsigned int) temp); + + addr = &ir_set->erst_base[0]; + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, " 0x%x: ir_set.erst_base[0] = 0x%x\n", + (unsigned int) addr, (unsigned int) temp); + + addr = &ir_set->erst_base[1]; + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, " 0x%x: ir_set.erst_base[1] = 0x%x\n", + (unsigned int) addr, (unsigned int) temp); + + addr = &ir_set->erst_dequeue[0]; + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, " 0x%x: ir_set.erst_dequeue[0] = 0x%x\n", + (unsigned int) addr, (unsigned int) temp); + + addr = &ir_set->erst_dequeue[1]; + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, " 0x%x: ir_set.erst_dequeue[1] = 0x%x\n", + (unsigned int) addr, (unsigned int) temp); +} + +void xhci_print_run_regs(struct xhci_hcd *xhci) +{ + u32 temp; + int i; + + xhci_dbg(xhci, "xHCI runtime registers at 0x%x:\n", + (unsigned int) xhci->run_regs); + temp = xhci_readl(xhci, &xhci->run_regs->microframe_index); + xhci_dbg(xhci, " 0x%x: Microframe index = 0x%x\n", + (unsigned int) &xhci->run_regs->microframe_index, + (unsigned int) temp); + for (i = 0; i < 7; ++i) { + temp = xhci_readl(xhci, &xhci->run_regs->rsvd[i]); + if (temp != XHCI_INIT_VALUE) + xhci_dbg(xhci, " WARN: 0x%x: Rsvd[%i] = 0x%x\n", + (unsigned int) &xhci->run_regs->rsvd[i], + i, (unsigned int) temp); + } +} + +void xhci_print_registers(struct xhci_hcd *xhci) +{ + xhci_print_cap_regs(xhci); + xhci_print_op_regs(xhci); +} diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h new file mode 100644 index 000000000000..ecc131c3fe33 --- /dev/null +++ b/drivers/usb/host/xhci-ext-caps.h @@ -0,0 +1,145 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/* Up to 16 microframes to halt an HC - one microframe is 125 microsectonds */ +#define XHCI_MAX_HALT_USEC (16*125) +/* HC not running - set to 1 when run/stop bit is cleared. */ +#define XHCI_STS_HALT (1<<0) + +/* HCCPARAMS offset from PCI base address */ +#define XHCI_HCC_PARAMS_OFFSET 0x10 +/* HCCPARAMS contains the first extended capability pointer */ +#define XHCI_HCC_EXT_CAPS(p) (((p)>>16)&0xffff) + +/* Command and Status registers offset from the Operational Registers address */ +#define XHCI_CMD_OFFSET 0x00 +#define XHCI_STS_OFFSET 0x04 + +#define XHCI_MAX_EXT_CAPS 50 + +/* Capability Register */ +/* bits 7:0 - how long is the Capabilities register */ +#define XHCI_HC_LENGTH(p) (((p)>>00)&0x00ff) + +/* Extended capability register fields */ +#define XHCI_EXT_CAPS_ID(p) (((p)>>0)&0xff) +#define XHCI_EXT_CAPS_NEXT(p) (((p)>>8)&0xff) +#define XHCI_EXT_CAPS_VAL(p) ((p)>>16) +/* Extended capability IDs - ID 0 reserved */ +#define XHCI_EXT_CAPS_LEGACY 1 +#define XHCI_EXT_CAPS_PROTOCOL 2 +#define XHCI_EXT_CAPS_PM 3 +#define XHCI_EXT_CAPS_VIRT 4 +#define XHCI_EXT_CAPS_ROUTE 5 +/* IDs 6-9 reserved */ +#define XHCI_EXT_CAPS_DEBUG 10 +/* USB Legacy Support Capability - section 7.1.1 */ +#define XHCI_HC_BIOS_OWNED (1 << 16) +#define XHCI_HC_OS_OWNED (1 << 24) + +/* USB Legacy Support Capability - section 7.1.1 */ +/* Add this offset, plus the value of xECP in HCCPARAMS to the base address */ +#define XHCI_LEGACY_SUPPORT_OFFSET (0x00) + +/* USB Legacy Support Control and Status Register - section 7.1.2 */ +/* Add this offset, plus the value of xECP in HCCPARAMS to the base address */ +#define XHCI_LEGACY_CONTROL_OFFSET (0x04) +/* bits 1:2, 5:12, and 17:19 need to be preserved; bits 21:28 should be zero */ +#define XHCI_LEGACY_DISABLE_SMI ((0x3 << 1) + (0xff << 5) + (0x7 << 17)) + +/* command register values to disable interrupts and halt the HC */ +/* start/stop HC execution - do not write unless HC is halted*/ +#define XHCI_CMD_RUN (1 << 0) +/* Event Interrupt Enable - get irq when EINT bit is set in USBSTS register */ +#define XHCI_CMD_EIE (1 << 2) +/* Host System Error Interrupt Enable - get irq when HSEIE bit set in USBSTS */ +#define XHCI_CMD_HSEIE (1 << 3) +/* Enable Wrap Event - '1' means xHC generates an event when MFINDEX wraps. */ +#define XHCI_CMD_EWE (1 << 10) + +#define XHCI_IRQS (XHCI_CMD_EIE | XHCI_CMD_HSEIE | XHCI_CMD_EWE) + +/* true: Controller Not Ready to accept doorbell or op reg writes after reset */ +#define XHCI_STS_CNR (1 << 11) + +#include <linux/io.h> + +/** + * Return the next extended capability pointer register. + * + * @base PCI register base address. + * + * @ext_offset Offset of the 32-bit register that contains the extended + * capabilites pointer. If searching for the first extended capability, pass + * in XHCI_HCC_PARAMS_OFFSET. If searching for the next extended capability, + * pass in the offset of the current extended capability register. + * + * Returns 0 if there is no next extended capability register or returns the register offset + * from the PCI registers base address. + */ +static inline int xhci_find_next_cap_offset(void __iomem *base, int ext_offset) +{ + u32 next; + + next = readl(base + ext_offset); + + if (ext_offset == XHCI_HCC_PARAMS_OFFSET) + /* Find the first extended capability */ + next = XHCI_HCC_EXT_CAPS(next); + else + /* Find the next extended capability */ + next = XHCI_EXT_CAPS_NEXT(next); + if (!next) + return 0; + /* + * Address calculation from offset of extended capabilities + * (or HCCPARAMS) register - see section 5.3.6 and section 7. + */ + return ext_offset + (next << 2); +} + +/** + * Find the offset of the extended capabilities with capability ID id. + * + * @base PCI MMIO registers base address. + * @ext_offset Offset from base of the first extended capability to look at, + * or the address of HCCPARAMS. + * @id Extended capability ID to search for. + * + * This uses an arbitrary limit of XHCI_MAX_EXT_CAPS extended capabilities + * to make sure that the list doesn't contain a loop. + */ +static inline int xhci_find_ext_cap_by_id(void __iomem *base, int ext_offset, int id) +{ + u32 val; + int limit = XHCI_MAX_EXT_CAPS; + + while (ext_offset && limit > 0) { + val = readl(base + ext_offset); + if (XHCI_EXT_CAPS_ID(val) == id) + break; + ext_offset = xhci_find_next_cap_offset(base, ext_offset); + limit--; + } + if (limit > 0) + return ext_offset; + return 0; +} diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h new file mode 100644 index 000000000000..a4d44aad0697 --- /dev/null +++ b/drivers/usb/host/xhci.h @@ -0,0 +1,452 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LINUX_XHCI_HCD_H +#define __LINUX_XHCI_HCD_H + +#include <linux/usb.h> + +#include "../core/hcd.h" +/* Code sharing between pci-quirks and xhci hcd */ +#include "xhci-ext-caps.h" + +/* xHCI PCI Configuration Registers */ +#define XHCI_SBRN_OFFSET (0x60) + +/* + * xHCI register interface. + * This corresponds to the eXtensible Host Controller Interface (xHCI) + * Revision 0.95 specification + * + * Registers should always be accessed with double word or quad word accesses. + * + * Some xHCI implementations may support 64-bit address pointers. Registers + * with 64-bit address pointers should be written to with dword accesses by + * writing the low dword first (ptr[0]), then the high dword (ptr[1]) second. + * xHCI implementations that do not support 64-bit address pointers will ignore + * the high dword, and write order is irrelevant. + */ + +/** + * struct xhci_cap_regs - xHCI Host Controller Capability Registers. + * @hc_capbase: length of the capabilities register and HC version number + * @hcs_params1: HCSPARAMS1 - Structural Parameters 1 + * @hcs_params2: HCSPARAMS2 - Structural Parameters 2 + * @hcs_params3: HCSPARAMS3 - Structural Parameters 3 + * @hcc_params: HCCPARAMS - Capability Parameters + * @db_off: DBOFF - Doorbell array offset + * @run_regs_off: RTSOFF - Runtime register space offset + */ +struct xhci_cap_regs { + u32 hc_capbase; + u32 hcs_params1; + u32 hcs_params2; + u32 hcs_params3; + u32 hcc_params; + u32 db_off; + u32 run_regs_off; + /* Reserved up to (CAPLENGTH - 0x1C) */ +} __attribute__ ((packed)); + +/* hc_capbase bitmasks */ +/* bits 7:0 - how long is the Capabilities register */ +#define HC_LENGTH(p) XHCI_HC_LENGTH(p) +/* bits 31:16 */ +#define HC_VERSION(p) (((p) >> 16) & 0xffff) + +/* HCSPARAMS1 - hcs_params1 - bitmasks */ +/* bits 0:7, Max Device Slots */ +#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) +#define HCS_SLOTS_MASK 0xff +/* bits 8:18, Max Interrupters */ +#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) +/* bits 24:31, Max Ports - max value is 0x7F = 127 ports */ +#define HCS_MAX_PORTS(p) (((p) >> 24) & 0x7f) + +/* HCSPARAMS2 - hcs_params2 - bitmasks */ +/* bits 0:3, frames or uframes that SW needs to queue transactions + * ahead of the HW to meet periodic deadlines */ +#define HCS_IST(p) (((p) >> 0) & 0xf) +/* bits 4:7, max number of Event Ring segments */ +#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) +/* bit 26 Scratchpad restore - for save/restore HW state - not used yet */ +/* bits 27:31 number of Scratchpad buffers SW must allocate for the HW */ + +/* HCSPARAMS3 - hcs_params3 - bitmasks */ +/* bits 0:7, Max U1 to U0 latency for the roothub ports */ +#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) +/* bits 16:31, Max U2 to U0 latency for the roothub ports */ +#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) + +/* HCCPARAMS - hcc_params - bitmasks */ +/* true: HC can use 64-bit address pointers */ +#define HCC_64BIT_ADDR(p) ((p) & (1 << 0)) +/* true: HC can do bandwidth negotiation */ +#define HCC_BANDWIDTH_NEG(p) ((p) & (1 << 1)) +/* true: HC uses 64-byte Device Context structures + * FIXME 64-byte context structures aren't supported yet. + */ +#define HCC_64BYTE_CONTEXT(p) ((p) & (1 << 2)) +/* true: HC has port power switches */ +#define HCC_PPC(p) ((p) & (1 << 3)) +/* true: HC has port indicators */ +#define HCS_INDICATOR(p) ((p) & (1 << 4)) +/* true: HC has Light HC Reset Capability */ +#define HCC_LIGHT_RESET(p) ((p) & (1 << 5)) +/* true: HC supports latency tolerance messaging */ +#define HCC_LTC(p) ((p) & (1 << 6)) +/* true: no secondary Stream ID Support */ +#define HCC_NSS(p) ((p) & (1 << 7)) +/* Max size for Primary Stream Arrays - 2^(n+1), where n is bits 12:15 */ +#define HCC_MAX_PSA (1 << ((((p) >> 12) & 0xf) + 1)) +/* Extended Capabilities pointer from PCI base - section 5.3.6 */ +#define HCC_EXT_CAPS(p) XHCI_HCC_EXT_CAPS(p) + +/* db_off bitmask - bits 0:1 reserved */ +#define DBOFF_MASK (~0x3) + +/* run_regs_off bitmask - bits 0:4 reserved */ +#define RTSOFF_MASK (~0x1f) + + +/* Number of registers per port */ +#define NUM_PORT_REGS 4 + +/** + * struct xhci_op_regs - xHCI Host Controller Operational Registers. + * @command: USBCMD - xHC command register + * @status: USBSTS - xHC status register + * @page_size: This indicates the page size that the host controller + * supports. If bit n is set, the HC supports a page size + * of 2^(n+12), up to a 128MB page size. + * 4K is the minimum page size. + * @cmd_ring: CRP - 64-bit Command Ring Pointer + * @dcbaa_ptr: DCBAAP - 64-bit Device Context Base Address Array Pointer + * @config_reg: CONFIG - Configure Register + * @port_status_base: PORTSCn - base address for Port Status and Control + * Each port has a Port Status and Control register, + * followed by a Port Power Management Status and Control + * register, a Port Link Info register, and a reserved + * register. + * @port_power_base: PORTPMSCn - base address for + * Port Power Management Status and Control + * @port_link_base: PORTLIn - base address for Port Link Info (current + * Link PM state and control) for USB 2.1 and USB 3.0 + * devices. + */ +struct xhci_op_regs { + u32 command; + u32 status; + u32 page_size; + u32 reserved1; + u32 reserved2; + u32 dev_notification; + u32 cmd_ring[2]; + /* rsvd: offset 0x20-2F */ + u32 reserved3[4]; + u32 dcbaa_ptr[2]; + u32 config_reg; + /* rsvd: offset 0x3C-3FF */ + u32 reserved4[241]; + /* port 1 registers, which serve as a base address for other ports */ + u32 port_status_base; + u32 port_power_base; + u32 port_link_base; + u32 reserved5; + /* registers for ports 2-255 */ + u32 reserved6[NUM_PORT_REGS*254]; +} __attribute__ ((packed)); + +/* USBCMD - USB command - command bitmasks */ +/* start/stop HC execution - do not write unless HC is halted*/ +#define CMD_RUN XHCI_CMD_RUN +/* Reset HC - resets internal HC state machine and all registers (except + * PCI config regs). HC does NOT drive a USB reset on the downstream ports. + * The xHCI driver must reinitialize the xHC after setting this bit. + */ +#define CMD_RESET (1 << 1) +/* Event Interrupt Enable - a '1' allows interrupts from the host controller */ +#define CMD_EIE XHCI_CMD_EIE +/* Host System Error Interrupt Enable - get out-of-band signal for HC errors */ +#define CMD_HSEIE XHCI_CMD_HSEIE +/* bits 4:6 are reserved (and should be preserved on writes). */ +/* light reset (port status stays unchanged) - reset completed when this is 0 */ +#define CMD_LRESET (1 << 7) +/* FIXME: ignoring host controller save/restore state for now. */ +#define CMD_CSS (1 << 8) +#define CMD_CRS (1 << 9) +/* Enable Wrap Event - '1' means xHC generates an event when MFINDEX wraps. */ +#define CMD_EWE XHCI_CMD_EWE +/* MFINDEX power management - '1' means xHC can stop MFINDEX counter if all root + * hubs are in U3 (selective suspend), disconnect, disabled, or powered-off. + * '0' means the xHC can power it off if all ports are in the disconnect, + * disabled, or powered-off state. + */ +#define CMD_PM_INDEX (1 << 11) +/* bits 12:31 are reserved (and should be preserved on writes). */ + +/* USBSTS - USB status - status bitmasks */ +/* HC not running - set to 1 when run/stop bit is cleared. */ +#define STS_HALT XHCI_STS_HALT +/* serious error, e.g. PCI parity error. The HC will clear the run/stop bit. */ +#define STS_FATAL (1 << 2) +/* event interrupt - clear this prior to clearing any IP flags in IR set*/ +#define STS_EINT (1 << 3) +/* port change detect */ +#define STS_PORT (1 << 4) +/* bits 5:7 reserved and zeroed */ +/* save state status - '1' means xHC is saving state */ +#define STS_SAVE (1 << 8) +/* restore state status - '1' means xHC is restoring state */ +#define STS_RESTORE (1 << 9) +/* true: save or restore error */ +#define STS_SRE (1 << 10) +/* true: Controller Not Ready to accept doorbell or op reg writes after reset */ +#define STS_CNR XHCI_STS_CNR +/* true: internal Host Controller Error - SW needs to reset and reinitialize */ +#define STS_HCE (1 << 12) +/* bits 13:31 reserved and should be preserved */ + +/* + * DNCTRL - Device Notification Control Register - dev_notification bitmasks + * Generate a device notification event when the HC sees a transaction with a + * notification type that matches a bit set in this bit field. + */ +#define DEV_NOTE_MASK (0xffff) +#define ENABLE_DEV_NOTE(x) (1 << x) +/* Most of the device notification types should only be used for debug. + * SW does need to pay attention to function wake notifications. + */ +#define DEV_NOTE_FWAKE ENABLE_DEV_NOTE(1) + +/* CONFIG - Configure Register - config_reg bitmasks */ +/* bits 0:7 - maximum number of device slots enabled (NumSlotsEn) */ +#define MAX_DEVS(p) ((p) & 0xff) +/* bits 8:31 - reserved and should be preserved */ + +/* PORTSC - Port Status and Control Register - port_status_base bitmasks */ +/* true: device connected */ +#define PORT_CONNECT (1 << 0) +/* true: port enabled */ +#define PORT_PE (1 << 1) +/* bit 2 reserved and zeroed */ +/* true: port has an over-current condition */ +#define PORT_OC (1 << 3) +/* true: port reset signaling asserted */ +#define PORT_RESET (1 << 4) +/* Port Link State - bits 5:8 + * A read gives the current link PM state of the port, + * a write with Link State Write Strobe set sets the link state. + */ +/* true: port has power (see HCC_PPC) */ +#define PORT_POWER (1 << 9) +/* bits 10:13 indicate device speed: + * 0 - undefined speed - port hasn't be initialized by a reset yet + * 1 - full speed + * 2 - low speed + * 3 - high speed + * 4 - super speed + * 5-15 reserved + */ +#define DEV_SPEED_MASK (0xf<<10) +#define DEV_UNDEFSPEED(p) (((p) & DEV_SPEED_MASK) == (0x0<<10)) +#define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == (0x1<<10)) +#define DEV_LOWSPEED(p) (((p) & DEV_SPEED_MASK) == (0x2<<10)) +#define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == (0x3<<10)) +#define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == (0x4<<10)) +/* Port Indicator Control */ +#define PORT_LED_OFF (0 << 14) +#define PORT_LED_AMBER (1 << 14) +#define PORT_LED_GREEN (2 << 14) +#define PORT_LED_MASK (3 << 14) +/* Port Link State Write Strobe - set this when changing link state */ +#define PORT_LINK_STROBE (1 << 16) +/* true: connect status change */ +#define PORT_CSC (1 << 17) +/* true: port enable change */ +#define PORT_PEC (1 << 18) +/* true: warm reset for a USB 3.0 device is done. A "hot" reset puts the port + * into an enabled state, and the device into the default state. A "warm" reset + * also resets the link, forcing the device through the link training sequence. + * SW can also look at the Port Reset register to see when warm reset is done. + */ +#define PORT_WRC (1 << 19) +/* true: over-current change */ +#define PORT_OCC (1 << 20) +/* true: reset change - 1 to 0 transition of PORT_RESET */ +#define PORT_RC (1 << 21) +/* port link status change - set on some port link state transitions: + * Transition Reason + * ------------------------------------------------------------------------------ + * - U3 to Resume Wakeup signaling from a device + * - Resume to Recovery to U0 USB 3.0 device resume + * - Resume to U0 USB 2.0 device resume + * - U3 to Recovery to U0 Software resume of USB 3.0 device complete + * - U3 to U0 Software resume of USB 2.0 device complete + * - U2 to U0 L1 resume of USB 2.1 device complete + * - U0 to U0 (???) L1 entry rejection by USB 2.1 device + * - U0 to disabled L1 entry error with USB 2.1 device + * - Any state to inactive Error on USB 3.0 port + */ +#define PORT_PLC (1 << 22) +/* port configure error change - port failed to configure its link partner */ +#define PORT_CEC (1 << 23) +/* bit 24 reserved */ +/* wake on connect (enable) */ +#define PORT_WKCONN_E (1 << 25) +/* wake on disconnect (enable) */ +#define PORT_WKDISC_E (1 << 26) +/* wake on over-current (enable) */ +#define PORT_WKOC_E (1 << 27) +/* bits 28:29 reserved */ +/* true: device is removable - for USB 3.0 roothub emulation */ +#define PORT_DEV_REMOVE (1 << 30) +/* Initiate a warm port reset - complete when PORT_WRC is '1' */ +#define PORT_WR (1 << 31) + +/* Port Power Management Status and Control - port_power_base bitmasks */ +/* Inactivity timer value for transitions into U1, in microseconds. + * Timeout can be up to 127us. 0xFF means an infinite timeout. + */ +#define PORT_U1_TIMEOUT(p) ((p) & 0xff) +/* Inactivity timer value for transitions into U2 */ +#define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) +/* Bits 24:31 for port testing */ + + +/** + * struct intr_reg - Interrupt Register Set + * @irq_pending: IMAN - Interrupt Management Register. Used to enable + * interrupts and check for pending interrupts. + * @irq_control: IMOD - Interrupt Moderation Register. + * Used to throttle interrupts. + * @erst_size: Number of segments in the Event Ring Segment Table (ERST). + * @erst_base: ERST base address. + * @erst_dequeue: Event ring dequeue pointer. + * + * Each interrupter (defined by a MSI-X vector) has an event ring and an Event + * Ring Segment Table (ERST) associated with it. The event ring is comprised of + * multiple segments of the same size. The HC places events on the ring and + * "updates the Cycle bit in the TRBs to indicate to software the current + * position of the Enqueue Pointer." The HCD (Linux) processes those events and + * updates the dequeue pointer. + */ +struct intr_reg { + u32 irq_pending; + u32 irq_control; + u32 erst_size; + u32 rsvd; + u32 erst_base[2]; + u32 erst_dequeue[2]; +} __attribute__ ((packed)); + +#define ER_IRQ_PENDING(p) ((p) & 0x1) +#define ER_IRQ_ENABLE(p) ((p) | 0x2) +/* Preserve bits 16:31 of erst_size */ +#define ERST_SIZE_MASK (0xffff<<16) + +/** + * struct xhci_run_regs + * @microframe_index: + * MFINDEX - current microframe number + * + * Section 5.5 Host Controller Runtime Registers: + * "Software should read and write these registers using only Dword (32 bit) + * or larger accesses" + */ +struct xhci_run_regs { + u32 microframe_index; + u32 rsvd[7]; + struct intr_reg ir_set[128]; +} __attribute__ ((packed)); + + +/* There is one ehci_hci structure per controller */ +struct xhci_hcd { + /* glue to PCI and HCD framework */ + struct xhci_cap_regs __iomem *cap_regs; + struct xhci_op_regs __iomem *op_regs; + struct xhci_run_regs __iomem *run_regs; + + /* Cached register copies of read-only HC data */ + __u32 hcs_params1; + __u32 hcs_params2; + __u32 hcs_params3; + __u32 hcc_params; + + spinlock_t lock; + + /* packed release number */ + u8 sbrn; + u16 hci_version; + u8 max_slots; + u8 max_interrupters; + u8 max_ports; + u8 isoc_threshold; + int event_ring_max; + int addr_64; + int page_size; +}; + +/* convert between an HCD pointer and the corresponding EHCI_HCD */ +static inline struct xhci_hcd *hcd_to_xhci(struct usb_hcd *hcd) +{ + return (struct xhci_hcd *) (hcd->hcd_priv); +} + +static inline struct usb_hcd *xhci_to_hcd(struct xhci_hcd *xhci) +{ + return container_of((void *) xhci, struct usb_hcd, hcd_priv); +} + +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING +#define XHCI_DEBUG 1 +#else +#define XHCI_DEBUG 0 +#endif + +#define xhci_dbg(xhci, fmt, args...) \ + do { if (XHCI_DEBUG) dev_dbg(xhci_to_hcd(xhci)->self.controller , fmt , ## args); } while (0) +#define xhci_info(xhci, fmt, args...) \ + do { if (XHCI_DEBUG) dev_info(xhci_to_hcd(xhci)->self.controller , fmt , ## args); } while (0) +#define xhci_err(xhci, fmt, args...) \ + dev_err(xhci_to_hcd(xhci)->self.controller , fmt , ## args) +#define xhci_warn(xhci, fmt, args...) \ + dev_warn(xhci_to_hcd(xhci)->self.controller , fmt , ## args) + +/* TODO: copied from ehci.h - can be refactored? */ +/* xHCI spec says all registers are little endian */ +static inline unsigned int xhci_readl(const struct xhci_hcd *xhci, + __u32 __iomem *regs) +{ + return readl(regs); +} +static inline void xhci_writel(const struct xhci_hcd *xhci, + const unsigned int val, __u32 __iomem *regs) +{ + if (!in_interrupt()) + xhci_dbg(xhci, "`MEM_WRITE_DWORD(3'b000, 32'h%0x, 32'h%0x, 4'hf);\n", + (unsigned int) regs, val); + writel(val, regs); +} + +#endif /* __LINUX_XHCI_HCD_H */ -- cgit v1.2.3 From 66d4eadd8d067269ea8fead1a50fe87c2979a80d Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:52:28 -0700 Subject: USB: xhci: BIOS handoff and HW initialization. Add PCI initialization code to take control of the xHCI host controller away from the BIOS, halt, and reset the host controller. The xHCI spec says that BIOSes must give up the host controller within 5 seconds. Add some host controller glue functions to handle hardware initialization and memory allocation for the host controller. The current xHCI prototypes use PCI interrupts, but the xHCI spec requires MSI-X interrupts. Add code to support MSI-X interrupts, but use the PCI interrupts for now. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hcd.h | 1 + drivers/usb/host/pci-quirks.c | 123 ++++++++++++++ drivers/usb/host/xhci-hcd.c | 377 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-mem.c | 75 +++++++++ drivers/usb/host/xhci-pci.c | 150 +++++++++++++++++ drivers/usb/host/xhci.h | 63 ++++++- include/linux/pci_ids.h | 1 + 7 files changed, 788 insertions(+), 2 deletions(-) create mode 100644 drivers/usb/host/xhci-hcd.c create mode 100644 drivers/usb/host/xhci-mem.c create mode 100644 drivers/usb/host/xhci-pci.c (limited to 'drivers') diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 174170f14f06..7a47498b0aac 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -173,6 +173,7 @@ struct hc_driver { #define HCD_LOCAL_MEM 0x0002 /* HC needs local memory */ #define HCD_USB11 0x0010 /* USB 1.1 */ #define HCD_USB2 0x0020 /* USB 2.0 */ +#define HCD_USB3 0x0040 /* USB 3.0 */ /* called to init HCD and root hub */ int (*reset) (struct usb_hcd *hcd); diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 033c2846ce59..83b5f9cea85a 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -15,6 +15,7 @@ #include <linux/delay.h> #include <linux/acpi.h> #include "pci-quirks.h" +#include "xhci-ext-caps.h" #define UHCI_USBLEGSUP 0xc0 /* legacy support */ @@ -341,7 +342,127 @@ static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) return; } +/* + * handshake - spin reading a register until handshake completes + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @wait_usec: timeout in microseconds + * @delay_usec: delay in microseconds to wait between polling + * + * Polls a register every delay_usec microseconds. + * Returns 0 when the mask bits have the value done. + * Returns -ETIMEDOUT if this condition is not true after + * wait_usec microseconds have passed. + */ +static int handshake(void __iomem *ptr, u32 mask, u32 done, + int wait_usec, int delay_usec) +{ + u32 result; + + do { + result = readl(ptr); + result &= mask; + if (result == done) + return 0; + udelay(delay_usec); + wait_usec -= delay_usec; + } while (wait_usec > 0); + return -ETIMEDOUT; +} + +/** + * PCI Quirks for xHCI. + * + * Takes care of the handoff between the Pre-OS (i.e. BIOS) and the OS. + * It signals to the BIOS that the OS wants control of the host controller, + * and then waits 5 seconds for the BIOS to hand over control. + * If we timeout, assume the BIOS is broken and take control anyway. + */ +static void __devinit quirk_usb_handoff_xhci(struct pci_dev *pdev) +{ + void __iomem *base; + int ext_cap_offset; + void __iomem *op_reg_base; + u32 val; + int timeout; + + if (!mmio_resource_enabled(pdev, 0)) + return; + + base = ioremap_nocache(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + if (base == NULL) + return; + /* + * Find the Legacy Support Capability register - + * this is optional for xHCI host controllers. + */ + ext_cap_offset = xhci_find_next_cap_offset(base, XHCI_HCC_PARAMS_OFFSET); + do { + if (!ext_cap_offset) + /* We've reached the end of the extended capabilities */ + goto hc_init; + val = readl(base + ext_cap_offset); + if (XHCI_EXT_CAPS_ID(val) == XHCI_EXT_CAPS_LEGACY) + break; + ext_cap_offset = xhci_find_next_cap_offset(base, ext_cap_offset); + } while (1); + + /* If the BIOS owns the HC, signal that the OS wants it, and wait */ + if (val & XHCI_HC_BIOS_OWNED) { + writel(val & XHCI_HC_OS_OWNED, base + ext_cap_offset); + + /* Wait for 5 seconds with 10 microsecond polling interval */ + timeout = handshake(base + ext_cap_offset, XHCI_HC_BIOS_OWNED, + 0, 5000, 10); + + /* Assume a buggy BIOS and take HC ownership anyway */ + if (timeout) { + dev_warn(&pdev->dev, "xHCI BIOS handoff failed" + " (BIOS bug ?) %08x\n", val); + writel(val & ~XHCI_HC_BIOS_OWNED, base + ext_cap_offset); + } + } + + /* Disable any BIOS SMIs */ + writel(XHCI_LEGACY_DISABLE_SMI, + base + ext_cap_offset + XHCI_LEGACY_CONTROL_OFFSET); + +hc_init: + op_reg_base = base + XHCI_HC_LENGTH(readl(base)); + + /* Wait for the host controller to be ready before writing any + * operational or runtime registers. Wait 5 seconds and no more. + */ + timeout = handshake(op_reg_base + XHCI_STS_OFFSET, XHCI_STS_CNR, 0, + 5000, 10); + /* Assume a buggy HC and start HC initialization anyway */ + if (timeout) { + val = readl(op_reg_base + XHCI_STS_OFFSET); + dev_warn(&pdev->dev, + "xHCI HW not ready after 5 sec (HC bug?) " + "status = 0x%x\n", val); + } + + /* Send the halt and disable interrupts command */ + val = readl(op_reg_base + XHCI_CMD_OFFSET); + val &= ~(XHCI_CMD_RUN | XHCI_IRQS); + writel(val, op_reg_base + XHCI_CMD_OFFSET); + + /* Wait for the HC to halt - poll every 125 usec (one microframe). */ + timeout = handshake(op_reg_base + XHCI_STS_OFFSET, XHCI_STS_HALT, 1, + XHCI_MAX_HALT_USEC, 125); + if (timeout) { + val = readl(op_reg_base + XHCI_STS_OFFSET); + dev_warn(&pdev->dev, + "xHCI HW did not halt within %d usec " + "status = 0x%x\n", XHCI_MAX_HALT_USEC, val); + } + + iounmap(base); +} static void __devinit quirk_usb_early_handoff(struct pci_dev *pdev) { @@ -351,5 +472,7 @@ static void __devinit quirk_usb_early_handoff(struct pci_dev *pdev) quirk_usb_handoff_ohci(pdev); else if (pdev->class == PCI_CLASS_SERIAL_USB_EHCI) quirk_usb_disable_ehci(pdev); + else if (pdev->class == PCI_CLASS_SERIAL_USB_XHCI) + quirk_usb_handoff_xhci(pdev); } DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, quirk_usb_early_handoff); diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c new file mode 100644 index 000000000000..64fcc22e9d59 --- /dev/null +++ b/drivers/usb/host/xhci-hcd.c @@ -0,0 +1,377 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/irq.h> +#include <linux/module.h> + +#include "xhci.h" + +#define DRIVER_AUTHOR "Sarah Sharp" +#define DRIVER_DESC "'eXtensible' Host Controller (xHC) Driver" + +/* TODO: copied from ehci-hcd.c - can this be refactored? */ +/* + * handshake - spin reading hc until handshake completes or fails + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + */ +static int handshake(struct xhci_hcd *xhci, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = xhci_readl(xhci, ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* + * Force HC into halt state. + * + * Disable any IRQs and clear the run/stop bit. + * HC will complete any current and actively pipelined transactions, and + * should halt within 16 microframes of the run/stop bit being cleared. + * Read HC Halted bit in the status register to see when the HC is finished. + * XXX: shouldn't we set HC_STATE_HALT here somewhere? + */ +int xhci_halt(struct xhci_hcd *xhci) +{ + u32 halted; + u32 cmd; + u32 mask; + + xhci_dbg(xhci, "// Halt the HC\n"); + /* Disable all interrupts from the host controller */ + mask = ~(XHCI_IRQS); + halted = xhci_readl(xhci, &xhci->op_regs->status) & STS_HALT; + if (!halted) + mask &= ~CMD_RUN; + + cmd = xhci_readl(xhci, &xhci->op_regs->command); + cmd &= mask; + xhci_writel(xhci, cmd, &xhci->op_regs->command); + + return handshake(xhci, &xhci->op_regs->status, + STS_HALT, STS_HALT, XHCI_MAX_HALT_USEC); +} + +/* + * Reset a halted HC, and set the internal HC state to HC_STATE_HALT. + * + * This resets pipelines, timers, counters, state machines, etc. + * Transactions will be terminated immediately, and operational registers + * will be set to their defaults. + */ +int xhci_reset(struct xhci_hcd *xhci) +{ + u32 command; + u32 state; + + state = xhci_readl(xhci, &xhci->op_regs->status); + BUG_ON((state & STS_HALT) == 0); + + xhci_dbg(xhci, "// Reset the HC\n"); + command = xhci_readl(xhci, &xhci->op_regs->command); + command |= CMD_RESET; + xhci_writel(xhci, command, &xhci->op_regs->command); + /* XXX: Why does EHCI set this here? Shouldn't other code do this? */ + xhci_to_hcd(xhci)->state = HC_STATE_HALT; + + return handshake(xhci, &xhci->op_regs->command, CMD_RESET, 0, 250 * 1000); +} + +/* + * Stop the HC from processing the endpoint queues. + */ +static void xhci_quiesce(struct xhci_hcd *xhci) +{ + /* + * Queues are per endpoint, so we need to disable an endpoint or slot. + * + * To disable a slot, we need to insert a disable slot command on the + * command ring and ring the doorbell. This will also free any internal + * resources associated with the slot (which might not be what we want). + * + * A Release Endpoint command sounds better - doesn't free internal HC + * memory, but removes the endpoints from the schedule and releases the + * bandwidth, disables the doorbells, and clears the endpoint enable + * flag. Usually used prior to a set interface command. + * + * TODO: Implement after command ring code is done. + */ + BUG_ON(!HC_IS_RUNNING(xhci_to_hcd(xhci)->state)); + xhci_dbg(xhci, "Finished quiescing -- code not written yet\n"); +} + +#if 0 +/* Set up MSI-X table for entry 0 (may claim other entries later) */ +static int xhci_setup_msix(struct xhci_hcd *xhci) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + + xhci->msix_count = 0; + /* XXX: did I do this right? ixgbe does kcalloc for more than one */ + xhci->msix_entries = kmalloc(sizeof(struct msix_entry), GFP_KERNEL); + if (!xhci->msix_entries) { + xhci_err(xhci, "Failed to allocate MSI-X entries\n"); + return -ENOMEM; + } + xhci->msix_entries[0].entry = 0; + + ret = pci_enable_msix(pdev, xhci->msix_entries, xhci->msix_count); + if (ret) { + xhci_err(xhci, "Failed to enable MSI-X\n"); + goto free_entries; + } + + /* + * Pass the xhci pointer value as the request_irq "cookie". + * If more irqs are added, this will need to be unique for each one. + */ + ret = request_irq(xhci->msix_entries[0].vector, &xhci_irq, 0, + "xHCI", xhci_to_hcd(xhci)); + if (ret) { + xhci_err(xhci, "Failed to allocate MSI-X interrupt\n"); + goto disable_msix; + } + xhci_dbg(xhci, "Finished setting up MSI-X\n"); + return 0; + +disable_msix: + pci_disable_msix(pdev); +free_entries: + kfree(xhci->msix_entries); + xhci->msix_entries = NULL; + return ret; +} + +/* XXX: code duplication; can xhci_setup_msix call this? */ +/* Free any IRQs and disable MSI-X */ +static void xhci_cleanup_msix(struct xhci_hcd *xhci) +{ + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + if (!xhci->msix_entries) + return; + + free_irq(xhci->msix_entries[0].vector, xhci); + pci_disable_msix(pdev); + kfree(xhci->msix_entries); + xhci->msix_entries = NULL; + xhci_dbg(xhci, "Finished cleaning up MSI-X\n"); +} +#endif + +/* + * Initialize memory for HCD and xHC (one-time init). + * + * Program the PAGESIZE register, initialize the device context array, create + * device contexts (?), set up a command ring segment (or two?), create event + * ring (one for now). + */ +int xhci_init(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int retval = 0; + + xhci_dbg(xhci, "xhci_init\n"); + spin_lock_init(&xhci->lock); + retval = xhci_mem_init(xhci, GFP_KERNEL); + xhci_dbg(xhci, "Finished xhci_init\n"); + + return retval; +} + +/* + * Start the HC after it was halted. + * + * This function is called by the USB core when the HC driver is added. + * Its opposite is xhci_stop(). + * + * xhci_init() must be called once before this function can be called. + * Reset the HC, enable device slot contexts, program DCBAAP, and + * set command ring pointer and event ring pointer. + * + * Setup MSI-X vectors and enable interrupts. + */ +int xhci_run(struct usb_hcd *hcd) +{ + u32 temp; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + xhci_dbg(xhci, "xhci_run\n"); + +#if 0 /* FIXME: MSI not setup yet */ + /* Do this at the very last minute */ + ret = xhci_setup_msix(xhci); + if (!ret) + return ret; + + return -ENOSYS; +#endif + xhci_dbg(xhci, "// Set the interrupt modulation register\n"); + temp = xhci_readl(xhci, &xhci->ir_set->irq_control); + temp &= 0xffff; + temp |= (u32) 160; + xhci_writel(xhci, temp, &xhci->ir_set->irq_control); + + /* Set the HCD state before we enable the irqs */ + hcd->state = HC_STATE_RUNNING; + temp = xhci_readl(xhci, &xhci->op_regs->command); + temp |= (CMD_EIE); + xhci_dbg(xhci, "// Enable interrupts, cmd = 0x%x.\n", + temp); + xhci_writel(xhci, temp, &xhci->op_regs->command); + + temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); + xhci_dbg(xhci, "// Enabling event ring interrupter 0x%x" + " by writing 0x%x to irq_pending\n", + (unsigned int) xhci->ir_set, + (unsigned int) ER_IRQ_ENABLE(temp)); + xhci_writel(xhci, ER_IRQ_ENABLE(temp), + &xhci->ir_set->irq_pending); + xhci_print_ir_set(xhci, xhci->ir_set, 0); + + temp = xhci_readl(xhci, &xhci->op_regs->command); + temp |= (CMD_RUN); + xhci_dbg(xhci, "// Turn on HC, cmd = 0x%x.\n", + temp); + xhci_writel(xhci, temp, &xhci->op_regs->command); + /* Flush PCI posted writes */ + temp = xhci_readl(xhci, &xhci->op_regs->command); + xhci_dbg(xhci, "// @%x = 0x%x\n", + (unsigned int) &xhci->op_regs->command, temp); + + xhci_dbg(xhci, "Finished xhci_run\n"); + return 0; +} + +/* + * Stop xHCI driver. + * + * This function is called by the USB core when the HC driver is removed. + * Its opposite is xhci_run(). + * + * Disable device contexts, disable IRQs, and quiesce the HC. + * Reset the HC, finish any completed transactions, and cleanup memory. + */ +void xhci_stop(struct usb_hcd *hcd) +{ + u32 temp; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + spin_lock_irq(&xhci->lock); + if (HC_IS_RUNNING(hcd->state)) + xhci_quiesce(xhci); + xhci_halt(xhci); + xhci_reset(xhci); + spin_unlock_irq(&xhci->lock); + +#if 0 /* No MSI yet */ + xhci_cleanup_msix(xhci); +#endif + xhci_dbg(xhci, "// Disabling event ring interrupts\n"); + temp = xhci_readl(xhci, &xhci->op_regs->status); + xhci_writel(xhci, temp & ~STS_EINT, &xhci->op_regs->status); + temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); + xhci_writel(xhci, ER_IRQ_DISABLE(temp), + &xhci->ir_set->irq_pending); + xhci_print_ir_set(xhci, xhci->ir_set, 0); + + xhci_dbg(xhci, "cleaning up memory\n"); + xhci_mem_cleanup(xhci); + xhci_dbg(xhci, "xhci_stop completed - status = %x\n", + xhci_readl(xhci, &xhci->op_regs->status)); +} + +/* + * Shutdown HC (not bus-specific) + * + * This is called when the machine is rebooting or halting. We assume that the + * machine will be powered off, and the HC's internal state will be reset. + * Don't bother to free memory. + */ +void xhci_shutdown(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + spin_lock_irq(&xhci->lock); + xhci_halt(xhci); + spin_unlock_irq(&xhci->lock); + +#if 0 + xhci_cleanup_msix(xhci); +#endif + + xhci_dbg(xhci, "xhci_shutdown completed - status = %x\n", + xhci_readl(xhci, &xhci->op_regs->status)); +} + +int xhci_get_frame(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + /* EHCI mods by the periodic size. Why? */ + return xhci_readl(xhci, &xhci->run_regs->microframe_index) >> 3; +} + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); + +static int __init xhci_hcd_init(void) +{ +#ifdef CONFIG_PCI + int retval = 0; + + retval = xhci_register_pci(); + + if (retval < 0) { + printk(KERN_DEBUG "Problem registering PCI driver."); + return retval; + } +#endif + return 0; +} +module_init(xhci_hcd_init); + +static void __exit xhci_hcd_cleanup(void) +{ +#ifdef CONFIG_PCI + xhci_unregister_pci(); +#endif +} +module_exit(xhci_hcd_cleanup); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c new file mode 100644 index 000000000000..0e383f9c380c --- /dev/null +++ b/drivers/usb/host/xhci-mem.c @@ -0,0 +1,75 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/usb.h> + +#include "xhci.h" + +void xhci_mem_cleanup(struct xhci_hcd *xhci) +{ + xhci->page_size = 0; + xhci->page_shift = 0; +} + +int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) +{ + unsigned int val, val2; + u32 page_size; + int i; + + page_size = xhci_readl(xhci, &xhci->op_regs->page_size); + xhci_dbg(xhci, "Supported page size register = 0x%x\n", page_size); + for (i = 0; i < 16; i++) { + if ((0x1 & page_size) != 0) + break; + page_size = page_size >> 1; + } + if (i < 16) + xhci_dbg(xhci, "Supported page size of %iK\n", (1 << (i+12)) / 1024); + else + xhci_warn(xhci, "WARN: no supported page size\n"); + /* Use 4K pages, since that's common and the minimum the HC supports */ + xhci->page_shift = 12; + xhci->page_size = 1 << xhci->page_shift; + xhci_dbg(xhci, "HCD page size set to %iK\n", xhci->page_size / 1024); + + /* + * Program the Number of Device Slots Enabled field in the CONFIG + * register with the max value of slots the HC can handle. + */ + val = HCS_MAX_SLOTS(xhci_readl(xhci, &xhci->cap_regs->hcs_params1)); + xhci_dbg(xhci, "// xHC can handle at most %d device slots.\n", + (unsigned int) val); + val2 = xhci_readl(xhci, &xhci->op_regs->config_reg); + val |= (val2 & ~HCS_SLOTS_MASK); + xhci_dbg(xhci, "// Setting Max device slots reg = 0x%x.\n", + (unsigned int) val); + xhci_writel(xhci, val, &xhci->op_regs->config_reg); + + xhci->ir_set = &xhci->run_regs->ir_set[0]; + + return 0; +fail: + xhci_warn(xhci, "Couldn't initialize memory\n"); + xhci_mem_cleanup(xhci); + return -ENOMEM; +} diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c new file mode 100644 index 000000000000..4015082adf60 --- /dev/null +++ b/drivers/usb/host/xhci-pci.c @@ -0,0 +1,150 @@ +/* + * xHCI host controller driver PCI Bus Glue. + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/pci.h> + +#include "xhci.h" + +static const char hcd_name[] = "xhci_hcd"; + +/* called after powerup, by probe or system-pm "wakeup" */ +static int xhci_pci_reinit(struct xhci_hcd *xhci, struct pci_dev *pdev) +{ + /* + * TODO: Implement finding debug ports later. + * TODO: see if there are any quirks that need to be added to handle + * new extended capabilities. + */ + + /* PCI Memory-Write-Invalidate cycle support is optional (uncommon) */ + if (!pci_set_mwi(pdev)) + xhci_dbg(xhci, "MWI active\n"); + + xhci_dbg(xhci, "Finished xhci_pci_reinit\n"); + return 0; +} + +/* called during probe() after chip reset completes */ +static int xhci_pci_setup(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + int retval; + + xhci->cap_regs = hcd->regs; + xhci->op_regs = hcd->regs + + HC_LENGTH(xhci_readl(xhci, &xhci->cap_regs->hc_capbase)); + xhci->run_regs = hcd->regs + + (xhci_readl(xhci, &xhci->cap_regs->run_regs_off) & RTSOFF_MASK); + /* Cache read-only capability registers */ + xhci->hcs_params1 = xhci_readl(xhci, &xhci->cap_regs->hcs_params1); + xhci->hcs_params2 = xhci_readl(xhci, &xhci->cap_regs->hcs_params2); + xhci->hcs_params3 = xhci_readl(xhci, &xhci->cap_regs->hcs_params3); + xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hcc_params); + xhci_print_registers(xhci); + + /* Make sure the HC is halted. */ + retval = xhci_halt(xhci); + if (retval) + return retval; + + xhci_dbg(xhci, "Resetting HCD\n"); + /* Reset the internal HC memory state and registers. */ + retval = xhci_reset(xhci); + if (retval) + return retval; + xhci_dbg(xhci, "Reset complete\n"); + + xhci_dbg(xhci, "Calling HCD init\n"); + /* Initialize HCD and host controller data structures. */ + retval = xhci_init(hcd); + if (retval) + return retval; + xhci_dbg(xhci, "Called HCD init\n"); + + pci_read_config_byte(pdev, XHCI_SBRN_OFFSET, &xhci->sbrn); + xhci_dbg(xhci, "Got SBRN %u\n", (unsigned int) xhci->sbrn); + + /* Find any debug ports */ + return xhci_pci_reinit(xhci, pdev); +} + +static const struct hc_driver xhci_pci_hc_driver = { + .description = hcd_name, + .product_desc = "xHCI Host Controller", + .hcd_priv_size = sizeof(struct xhci_hcd), + + /* + * generic hardware linkage + */ + .flags = HCD_MEMORY | HCD_USB3, + + /* + * basic lifecycle operations + */ + .reset = xhci_pci_setup, + .start = xhci_run, + /* suspend and resume implemented later */ + .stop = xhci_stop, + .shutdown = xhci_shutdown, + + /* + * scheduling support + */ + .get_frame_number = xhci_get_frame, + + /* Implement root hub support later. */ +}; + +/*-------------------------------------------------------------------------*/ + +/* PCI driver selection metadata; PCI hotplugging uses this */ +static const struct pci_device_id pci_ids[] = { { + /* handle any USB 3.0 xHCI controller */ + PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_XHCI, ~0), + .driver_data = (unsigned long) &xhci_pci_hc_driver, + }, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver xhci_pci_driver = { + .name = (char *) hcd_name, + .id_table = pci_ids, + + .probe = usb_hcd_pci_probe, + .remove = usb_hcd_pci_remove, + /* suspend and resume implemented later */ + + .shutdown = usb_hcd_pci_shutdown, +}; + +int xhci_register_pci() +{ + return pci_register_driver(&xhci_pci_driver); +} + +void xhci_unregister_pci() +{ + pci_unregister_driver(&xhci_pci_driver); +} diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index a4d44aad0697..59fae2e5ea59 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -32,6 +32,9 @@ /* xHCI PCI Configuration Registers */ #define XHCI_SBRN_OFFSET (0x60) +/* Max number of USB devices for any host controller - limit in section 6.1 */ +#define MAX_HC_SLOTS 256 + /* * xHCI register interface. * This corresponds to the eXtensible Host Controller Interface (xHCI) @@ -359,10 +362,35 @@ struct intr_reg { u32 erst_dequeue[2]; } __attribute__ ((packed)); +/* irq_pending bitmasks */ #define ER_IRQ_PENDING(p) ((p) & 0x1) -#define ER_IRQ_ENABLE(p) ((p) | 0x2) +/* bits 2:31 need to be preserved */ +#define ER_IRQ_CLEAR(p) ((p) & 0xfffffffe) +#define ER_IRQ_ENABLE(p) ((ER_IRQ_CLEAR(p)) | 0x2) +#define ER_IRQ_DISABLE(p) ((ER_IRQ_CLEAR(p)) & ~(0x2)) + +/* irq_control bitmasks */ +/* Minimum interval between interrupts (in 250ns intervals). The interval + * between interrupts will be longer if there are no events on the event ring. + * Default is 4000 (1 ms). + */ +#define ER_IRQ_INTERVAL_MASK (0xffff) +/* Counter used to count down the time to the next interrupt - HW use only */ +#define ER_IRQ_COUNTER_MASK (0xffff << 16) + +/* erst_size bitmasks */ /* Preserve bits 16:31 of erst_size */ -#define ERST_SIZE_MASK (0xffff<<16) +#define ERST_SIZE_MASK (0xffff << 16) + +/* erst_dequeue bitmasks */ +/* Dequeue ERST Segment Index (DESI) - Segment number (or alias) + * where the current dequeue pointer lies. This is an optional HW hint. + */ +#define ERST_DESI_MASK (0x7) +/* Event Handler Busy (EHB) - is the event ring scheduled to be serviced by + * a work queue (or delayed service routine)? + */ +#define ERST_EHB (1 << 3) /** * struct xhci_run_regs @@ -386,6 +414,8 @@ struct xhci_hcd { struct xhci_cap_regs __iomem *cap_regs; struct xhci_op_regs __iomem *op_regs; struct xhci_run_regs __iomem *run_regs; + /* Our HCD's current interrupter register set */ + struct intr_reg __iomem *ir_set; /* Cached register copies of read-only HC data */ __u32 hcs_params1; @@ -404,7 +434,13 @@ struct xhci_hcd { u8 isoc_threshold; int event_ring_max; int addr_64; + /* 4KB min, 128MB max */ int page_size; + /* Valid values are 12 to 20, inclusive */ + int page_shift; + /* only one MSI vector for now, but might need more later */ + int msix_count; + struct msix_entry *msix_entries; }; /* convert between an HCD pointer and the corresponding EHCI_HCD */ @@ -449,4 +485,27 @@ static inline void xhci_writel(const struct xhci_hcd *xhci, writel(val, regs); } +/* xHCI debugging */ +void xhci_print_ir_set(struct xhci_hcd *xhci, struct intr_reg *ir_set, int set_num); +void xhci_print_registers(struct xhci_hcd *xhci); + +/* xHCI memory managment */ +void xhci_mem_cleanup(struct xhci_hcd *xhci); +int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags); + +#ifdef CONFIG_PCI +/* xHCI PCI glue */ +int xhci_register_pci(void); +void xhci_unregister_pci(void); +#endif + +/* xHCI host controller glue */ +int xhci_halt(struct xhci_hcd *xhci); +int xhci_reset(struct xhci_hcd *xhci); +int xhci_init(struct usb_hcd *hcd); +int xhci_run(struct usb_hcd *hcd); +void xhci_stop(struct usb_hcd *hcd); +void xhci_shutdown(struct usb_hcd *hcd); +int xhci_get_frame(struct usb_hcd *hcd); + #endif /* __LINUX_XHCI_HCD_H */ diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index aa01d38c9971..a3b000365795 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -104,6 +104,7 @@ #define PCI_CLASS_SERIAL_USB_UHCI 0x0c0300 #define PCI_CLASS_SERIAL_USB_OHCI 0x0c0310 #define PCI_CLASS_SERIAL_USB_EHCI 0x0c0320 +#define PCI_CLASS_SERIAL_USB_XHCI 0x0c0330 #define PCI_CLASS_SERIAL_FIBER 0x0c04 #define PCI_CLASS_SERIAL_SMBUS 0x0c05 -- cgit v1.2.3 From 0ebbab37422315a5d0cb29792271085bafdf38c0 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:52:34 -0700 Subject: USB: xhci: Ring allocation and initialization. Allocate basic xHCI host controller data structures. For every xHC, there is a command ring, an event ring, and a doorbell array. The doorbell array is used to notify the host controller that work has been enqueued onto one of the rings. The host controller driver enqueues commands on the command ring. The HW enqueues command completion events on the event ring and interrupts the system (currently using PCI interrupts, although the xHCI HW will use MSI interrupts eventually). All rings and the doorbell array must be allocated by the xHCI host controller driver. Each ring is comprised of one or more segments, which consists of 16-byte Transfer Request Blocks (TRBs) that can be chained to form a Transfer Descriptor (TD) that represents a multiple-buffer request. Segments are linked into a ring using Link TRBs, which means they are dynamically growable. The producer of the ring enqueues a TD by writing one or more TRBs in the ring and toggling the TRB cycle bit for each TRB. The consumer knows it can process the TRB when the cycle bit matches its internal consumer cycle state for the ring. The consumer cycle state is toggled an odd amount of times in the ring. An example ring (a ring must have a minimum of 16 TRBs on it, but that's too big to draw in ASCII art): chain cycle bit bit ------------------------ | TD A TRB 1 | 1 | 1 |<------------- <-- consumer dequeue ptr ------------------------ | consumer cycle state = 1 | TD A TRB 2 | 1 | 1 | | ------------------------ | | TD A TRB 3 | 0 | 1 | segment 1 | ------------------------ | | TD B TRB 1 | 1 | 1 | | ------------------------ | | TD B TRB 2 | 0 | 1 | | ------------------------ | | Link TRB | 0 | 1 |----- | ------------------------ | | | | chain cycle | | bit bit | | ------------------------ | | | TD C TRB 1 | 0 | 1 |<---- | ------------------------ | | TD D TRB 1 | 1 | 1 | | ------------------------ | | TD D TRB 2 | 1 | 1 | segment 2 | ------------------------ | | TD D TRB 3 | 1 | 1 | | ------------------------ | | TD D TRB 4 | 1 | 1 | | ------------------------ | | Link TRB | 1 | 1 |----- | ------------------------ | | | | chain cycle | | bit bit | | ------------------------ | | | TD D TRB 5 | 1 | 1 |<---- | ------------------------ | | TD D TRB 6 | 0 | 1 | | ------------------------ | | TD E TRB 1 | 0 | 1 | segment 3 | ------------------------ | | | 0 | 0 | | <-- producer enqueue ptr ------------------------ | | | 0 | 0 | | ------------------------ | | Link TRB | 0 | 0 |--------------- ------------------------ Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-dbg.c | 81 ++++++++++++ drivers/usb/host/xhci-hcd.c | 5 + drivers/usb/host/xhci-mem.c | 305 +++++++++++++++++++++++++++++++++++++++++++- drivers/usb/host/xhci.h | 296 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 686 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index a7798b460492..5724683cef16 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -56,6 +56,8 @@ void xhci_dbg_regs(struct xhci_hcd *xhci) temp = xhci_readl(xhci, &xhci->cap_regs->db_off); xhci_dbg(xhci, "// @%x = 0x%x DBOFF\n", (unsigned int) &xhci->cap_regs->db_off, temp); + xhci_dbg(xhci, "// Doorbell array at 0x%x:\n", + (unsigned int) xhci->dba); } void xhci_print_cap_regs(struct xhci_hcd *xhci) @@ -227,3 +229,82 @@ void xhci_print_registers(struct xhci_hcd *xhci) xhci_print_cap_regs(xhci); xhci_print_op_regs(xhci); } + + +/** + * Debug a segment with an xHCI ring. + * + * @return The Link TRB of the segment, or NULL if there is no Link TRB + * (which is a bug, since all segments must have a Link TRB). + * + * Prints out all TRBs in the segment, even those after the Link TRB. + * + * XXX: should we print out TRBs that the HC owns? As long as we don't + * write, that should be fine... We shouldn't expect that the memory pointed to + * by the TRB is valid at all. Do we care about ones the HC owns? Probably, + * for HC debugging. + */ +void xhci_debug_segment(struct xhci_hcd *xhci, struct xhci_segment *seg) +{ + int i; + u32 addr = (u32) seg->dma; + union xhci_trb *trb = seg->trbs; + + for (i = 0; i < TRBS_PER_SEGMENT; ++i) { + trb = &seg->trbs[i]; + xhci_dbg(xhci, "@%08x %08x %08x %08x %08x\n", addr, + (unsigned int) trb->link.segment_ptr[0], + (unsigned int) trb->link.segment_ptr[1], + (unsigned int) trb->link.intr_target, + (unsigned int) trb->link.control); + addr += sizeof(*trb); + } +} + +/** + * Debugging for an xHCI ring, which is a queue broken into multiple segments. + * + * Print out each segment in the ring. Check that the DMA address in + * each link segment actually matches the segment's stored DMA address. + * Check that the link end bit is only set at the end of the ring. + * Check that the dequeue and enqueue pointers point to real data in this ring + * (not some other ring). + */ +void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + /* FIXME: Throw an error if any segment doesn't have a Link TRB */ + struct xhci_segment *seg; + struct xhci_segment *first_seg = ring->first_seg; + xhci_debug_segment(xhci, first_seg); + + for (seg = first_seg->next; seg != first_seg; seg = seg->next) + xhci_debug_segment(xhci, seg); +} + +void xhci_dbg_erst(struct xhci_hcd *xhci, struct xhci_erst *erst) +{ + u32 addr = (u32) erst->erst_dma_addr; + int i; + struct xhci_erst_entry *entry; + + for (i = 0; i < erst->num_entries; ++i) { + entry = &erst->entries[i]; + xhci_dbg(xhci, "@%08x %08x %08x %08x %08x\n", + (unsigned int) addr, + (unsigned int) entry->seg_addr[0], + (unsigned int) entry->seg_addr[1], + (unsigned int) entry->seg_size, + (unsigned int) entry->rsvd); + addr += sizeof(*entry); + } +} + +void xhci_dbg_cmd_ptrs(struct xhci_hcd *xhci) +{ + u32 val; + + val = xhci_readl(xhci, &xhci->op_regs->cmd_ring[0]); + xhci_dbg(xhci, "// xHC command ring deq ptr low bits + flags = 0x%x\n", val); + val = xhci_readl(xhci, &xhci->op_regs->cmd_ring[1]); + xhci_dbg(xhci, "// xHC command ring deq ptr high bits = 0x%x\n", val); +} diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 64fcc22e9d59..011f47810665 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -266,6 +266,11 @@ int xhci_run(struct usb_hcd *hcd) &xhci->ir_set->irq_pending); xhci_print_ir_set(xhci, xhci->ir_set, 0); + xhci_dbg(xhci, "Command ring memory map follows:\n"); + xhci_debug_ring(xhci, xhci->cmd_ring); + xhci_dbg(xhci, "ERST memory map follows:\n"); + xhci_dbg_erst(xhci, &xhci->erst); + temp = xhci_readl(xhci, &xhci->op_regs->command); temp |= (CMD_RUN); xhci_dbg(xhci, "// Turn on HC, cmd = 0x%x.\n", diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 0e383f9c380c..7cf15ca854be 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -21,18 +21,215 @@ */ #include <linux/usb.h> +#include <linux/pci.h> #include "xhci.h" +/* + * Allocates a generic ring segment from the ring pool, sets the dma address, + * initializes the segment to zero, and sets the private next pointer to NULL. + * + * Section 4.11.1.1: + * "All components of all Command and Transfer TRBs shall be initialized to '0'" + */ +static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, gfp_t flags) +{ + struct xhci_segment *seg; + dma_addr_t dma; + + seg = kzalloc(sizeof *seg, flags); + if (!seg) + return 0; + xhci_dbg(xhci, "Allocating priv segment structure at 0x%x\n", + (unsigned int) seg); + + seg->trbs = dma_pool_alloc(xhci->segment_pool, flags, &dma); + if (!seg->trbs) { + kfree(seg); + return 0; + } + xhci_dbg(xhci, "// Allocating segment at 0x%x (virtual) 0x%x (DMA)\n", + (unsigned int) seg->trbs, (u32) dma); + + memset(seg->trbs, 0, SEGMENT_SIZE); + seg->dma = dma; + seg->next = NULL; + + return seg; +} + +static void xhci_segment_free(struct xhci_hcd *xhci, struct xhci_segment *seg) +{ + if (!seg) + return; + if (seg->trbs) { + xhci_dbg(xhci, "Freeing DMA segment at 0x%x" + " (virtual) 0x%x (DMA)\n", + (unsigned int) seg->trbs, (u32) seg->dma); + dma_pool_free(xhci->segment_pool, seg->trbs, seg->dma); + seg->trbs = NULL; + } + xhci_dbg(xhci, "Freeing priv segment structure at 0x%x\n", + (unsigned int) seg); + kfree(seg); +} + +/* + * Make the prev segment point to the next segment. + * + * Change the last TRB in the prev segment to be a Link TRB which points to the + * DMA address of the next segment. The caller needs to set any Link TRB + * related flags, such as End TRB, Toggle Cycle, and no snoop. + */ +static void xhci_link_segments(struct xhci_hcd *xhci, struct xhci_segment *prev, + struct xhci_segment *next, bool link_trbs) +{ + u32 val; + + if (!prev || !next) + return; + prev->next = next; + if (link_trbs) { + prev->trbs[TRBS_PER_SEGMENT-1].link.segment_ptr[0] = next->dma; + + /* Set the last TRB in the segment to have a TRB type ID of Link TRB */ + val = prev->trbs[TRBS_PER_SEGMENT-1].link.control; + val &= ~TRB_TYPE_BITMASK; + val |= TRB_TYPE(TRB_LINK); + prev->trbs[TRBS_PER_SEGMENT-1].link.control = val; + } + xhci_dbg(xhci, "Linking segment 0x%x to segment 0x%x (DMA)\n", + prev->dma, next->dma); +} + +/* XXX: Do we need the hcd structure in all these functions? */ +static void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + struct xhci_segment *seg; + struct xhci_segment *first_seg; + + if (!ring || !ring->first_seg) + return; + first_seg = ring->first_seg; + seg = first_seg->next; + xhci_dbg(xhci, "Freeing ring at 0x%x\n", (unsigned int) ring); + while (seg != first_seg) { + struct xhci_segment *next = seg->next; + xhci_segment_free(xhci, seg); + seg = next; + } + xhci_segment_free(xhci, first_seg); + ring->first_seg = NULL; + kfree(ring); +} + +/** + * Create a new ring with zero or more segments. + * + * Link each segment together into a ring. + * Set the end flag and the cycle toggle bit on the last segment. + * See section 4.9.1 and figures 15 and 16. + */ +static struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, + unsigned int num_segs, bool link_trbs, gfp_t flags) +{ + struct xhci_ring *ring; + struct xhci_segment *prev; + + ring = kzalloc(sizeof *(ring), flags); + xhci_dbg(xhci, "Allocating ring at 0x%x\n", (unsigned int) ring); + if (!ring) + return 0; + + if (num_segs == 0) + return ring; + + ring->first_seg = xhci_segment_alloc(xhci, flags); + if (!ring->first_seg) + goto fail; + num_segs--; + + prev = ring->first_seg; + while (num_segs > 0) { + struct xhci_segment *next; + + next = xhci_segment_alloc(xhci, flags); + if (!next) + goto fail; + xhci_link_segments(xhci, prev, next, link_trbs); + + prev = next; + num_segs--; + } + xhci_link_segments(xhci, prev, ring->first_seg, link_trbs); + + if (link_trbs) { + /* See section 4.9.2.1 and 6.4.4.1 */ + prev->trbs[TRBS_PER_SEGMENT-1].link.control |= (LINK_TOGGLE); + xhci_dbg(xhci, "Wrote link toggle flag to" + " segment 0x%x (virtual), 0x%x (DMA)\n", + (unsigned int) prev, (u32) prev->dma); + } + /* The ring is empty, so the enqueue pointer == dequeue pointer */ + ring->enqueue = ring->first_seg->trbs; + ring->dequeue = ring->enqueue; + /* The ring is initialized to 0. The producer must write 1 to the cycle + * bit to handover ownership of the TRB, so PCS = 1. The consumer must + * compare CCS to the cycle bit to check ownership, so CCS = 1. + */ + ring->cycle_state = 1; + + return ring; + +fail: + xhci_ring_free(xhci, ring); + return 0; +} + void xhci_mem_cleanup(struct xhci_hcd *xhci) { + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + int size; + + /* XXX: Free all the segments in the various rings */ + + /* Free the Event Ring Segment Table and the actual Event Ring */ + xhci_writel(xhci, 0, &xhci->ir_set->erst_size); + xhci_writel(xhci, 0, &xhci->ir_set->erst_base[1]); + xhci_writel(xhci, 0, &xhci->ir_set->erst_base[0]); + xhci_writel(xhci, 0, &xhci->ir_set->erst_dequeue[1]); + xhci_writel(xhci, 0, &xhci->ir_set->erst_dequeue[0]); + size = sizeof(struct xhci_erst_entry)*(xhci->erst.num_entries); + if (xhci->erst.entries) + pci_free_consistent(pdev, size, + xhci->erst.entries, xhci->erst.erst_dma_addr); + xhci->erst.entries = NULL; + xhci_dbg(xhci, "Freed ERST\n"); + if (xhci->event_ring) + xhci_ring_free(xhci, xhci->event_ring); + xhci->event_ring = NULL; + xhci_dbg(xhci, "Freed event ring\n"); + + xhci_writel(xhci, 0, &xhci->op_regs->cmd_ring[1]); + xhci_writel(xhci, 0, &xhci->op_regs->cmd_ring[0]); + if (xhci->cmd_ring) + xhci_ring_free(xhci, xhci->cmd_ring); + xhci->cmd_ring = NULL; + xhci_dbg(xhci, "Freed command ring\n"); + if (xhci->segment_pool) + dma_pool_destroy(xhci->segment_pool); + xhci->segment_pool = NULL; + xhci_dbg(xhci, "Freed segment pool\n"); xhci->page_size = 0; xhci->page_shift = 0; } int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) { + dma_addr_t dma; + struct device *dev = xhci_to_hcd(xhci)->self.controller; unsigned int val, val2; + struct xhci_segment *seg; u32 page_size; int i; @@ -65,7 +262,113 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) (unsigned int) val); xhci_writel(xhci, val, &xhci->op_regs->config_reg); - xhci->ir_set = &xhci->run_regs->ir_set[0]; + /* + * Initialize the ring segment pool. The ring must be a contiguous + * structure comprised of TRBs. The TRBs must be 16 byte aligned, + * however, the command ring segment needs 64-byte aligned segments, + * so we pick the greater alignment need. + */ + xhci->segment_pool = dma_pool_create("xHCI ring segments", dev, + SEGMENT_SIZE, 64, xhci->page_size); + if (!xhci->segment_pool) + goto fail; + + /* Set up the command ring to have one segments for now. */ + xhci->cmd_ring = xhci_ring_alloc(xhci, 1, true, flags); + if (!xhci->cmd_ring) + goto fail; + xhci_dbg(xhci, "Allocated command ring at 0x%x\n", (unsigned int) xhci->cmd_ring); + xhci_dbg(xhci, "First segment DMA is 0x%x\n", (unsigned int) xhci->cmd_ring->first_seg->dma); + + /* Set the address in the Command Ring Control register */ + val = xhci_readl(xhci, &xhci->op_regs->cmd_ring[0]); + val = (val & ~CMD_RING_ADDR_MASK) | + (xhci->cmd_ring->first_seg->dma & CMD_RING_ADDR_MASK) | + xhci->cmd_ring->cycle_state; + xhci_dbg(xhci, "// Setting command ring address high bits to 0x0\n"); + xhci_writel(xhci, (u32) 0, &xhci->op_regs->cmd_ring[1]); + xhci_dbg(xhci, "// Setting command ring address low bits to 0x%x\n", val); + xhci_writel(xhci, val, &xhci->op_regs->cmd_ring[0]); + xhci_dbg_cmd_ptrs(xhci); + + val = xhci_readl(xhci, &xhci->cap_regs->db_off); + val &= DBOFF_MASK; + xhci_dbg(xhci, "// Doorbell array is located at offset 0x%x" + " from cap regs base addr\n", val); + xhci->dba = (void *) xhci->cap_regs + val; + xhci_dbg_regs(xhci); + xhci_print_run_regs(xhci); + /* Set ir_set to interrupt register set 0 */ + xhci->ir_set = (void *) xhci->run_regs->ir_set; + + /* + * Event ring setup: Allocate a normal ring, but also setup + * the event ring segment table (ERST). Section 4.9.3. + */ + xhci_dbg(xhci, "// Allocating event ring\n"); + xhci->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, false, flags); + if (!xhci->event_ring) + goto fail; + + xhci->erst.entries = pci_alloc_consistent(to_pci_dev(dev), + sizeof(struct xhci_erst_entry)*ERST_NUM_SEGS, &dma); + if (!xhci->erst.entries) + goto fail; + xhci_dbg(xhci, "// Allocated event ring segment table at 0x%x\n", dma); + + memset(xhci->erst.entries, 0, sizeof(struct xhci_erst_entry)*ERST_NUM_SEGS); + xhci->erst.num_entries = ERST_NUM_SEGS; + xhci->erst.erst_dma_addr = dma; + xhci_dbg(xhci, "Set ERST to 0; private num segs = %i, virt addr = 0x%x, dma addr = 0x%x\n", + xhci->erst.num_entries, + (unsigned int) xhci->erst.entries, + xhci->erst.erst_dma_addr); + + /* set ring base address and size for each segment table entry */ + for (val = 0, seg = xhci->event_ring->first_seg; val < ERST_NUM_SEGS; val++) { + struct xhci_erst_entry *entry = &xhci->erst.entries[val]; + entry->seg_addr[1] = 0; + entry->seg_addr[0] = seg->dma; + entry->seg_size = TRBS_PER_SEGMENT; + entry->rsvd = 0; + seg = seg->next; + } + + /* set ERST count with the number of entries in the segment table */ + val = xhci_readl(xhci, &xhci->ir_set->erst_size); + val &= ERST_SIZE_MASK; + val |= ERST_NUM_SEGS; + xhci_dbg(xhci, "// Write ERST size = %i to ir_set 0 (some bits preserved)\n", + val); + xhci_writel(xhci, val, &xhci->ir_set->erst_size); + + xhci_dbg(xhci, "// Set ERST entries to point to event ring.\n"); + /* set the segment table base address */ + xhci_dbg(xhci, "// Set ERST base address for ir_set 0 = 0x%x\n", + xhci->erst.erst_dma_addr); + xhci_writel(xhci, 0, &xhci->ir_set->erst_base[1]); + val = xhci_readl(xhci, &xhci->ir_set->erst_base[0]); + val &= ERST_PTR_MASK; + val |= (xhci->erst.erst_dma_addr & ~ERST_PTR_MASK); + xhci_writel(xhci, val, &xhci->ir_set->erst_base[0]); + + /* Set the event ring dequeue address */ + xhci_dbg(xhci, "// Set ERST dequeue address for ir_set 0 = 0x%x%x\n", + xhci->erst.entries[0].seg_addr[1], xhci->erst.entries[0].seg_addr[0]); + val = xhci_readl(xhci, &xhci->run_regs->ir_set[0].erst_dequeue[0]); + val &= ERST_PTR_MASK; + val |= (xhci->erst.entries[0].seg_addr[0] & ~ERST_PTR_MASK); + xhci_writel(xhci, val, &xhci->run_regs->ir_set[0].erst_dequeue[0]); + xhci_writel(xhci, xhci->erst.entries[0].seg_addr[1], + &xhci->run_regs->ir_set[0].erst_dequeue[1]); + xhci_dbg(xhci, "Wrote ERST address to ir_set 0.\n"); + xhci_print_ir_set(xhci, xhci->ir_set, 0); + + /* + * XXX: Might need to set the Interrupter Moderation Register to + * something other than the default (~1ms minimum between interrupts). + * See section 5.5.1.2. + */ return 0; fail: diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 59fae2e5ea59..ed331310f1a8 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -241,6 +241,18 @@ struct xhci_op_regs { */ #define DEV_NOTE_FWAKE ENABLE_DEV_NOTE(1) +/* CRCR - Command Ring Control Register - cmd_ring bitmasks */ +/* bit 0 is the command ring cycle state */ +/* stop ring operation after completion of the currently executing command */ +#define CMD_RING_PAUSE (1 << 1) +/* stop ring immediately - abort the currently executing command */ +#define CMD_RING_ABORT (1 << 2) +/* true: command ring is running */ +#define CMD_RING_RUNNING (1 << 3) +/* bits 4:5 reserved and should be preserved */ +/* Command Ring pointer - bit mask for the lower 32 bits. */ +#define CMD_RING_ADDR_MASK (0xffffffc0) + /* CONFIG - Configure Register - config_reg bitmasks */ /* bits 0:7 - maximum number of device slots enabled (NumSlotsEn) */ #define MAX_DEVS(p) ((p) & 0xff) @@ -391,6 +403,7 @@ struct intr_reg { * a work queue (or delayed service routine)? */ #define ERST_EHB (1 << 3) +#define ERST_PTR_MASK (0xf) /** * struct xhci_run_regs @@ -407,6 +420,275 @@ struct xhci_run_regs { struct intr_reg ir_set[128]; } __attribute__ ((packed)); +/** + * struct doorbell_array + * + * Section 5.6 + */ +struct xhci_doorbell_array { + u32 doorbell[256]; +} __attribute__ ((packed)); + +#define DB_TARGET_MASK 0xFFFFFF00 +#define DB_STREAM_ID_MASK 0x0000FFFF +#define DB_TARGET_HOST 0x0 +#define DB_STREAM_ID_HOST 0x0 +#define DB_MASK (0xff << 8) + + +struct xhci_transfer_event { + /* 64-bit buffer address, or immediate data */ + u32 buffer[2]; + u32 transfer_len; + /* This field is interpreted differently based on the type of TRB */ + u32 flags; +} __attribute__ ((packed)); + +/* Completion Code - only applicable for some types of TRBs */ +#define COMP_CODE_MASK (0xff << 24) +#define GET_COMP_CODE(p) (((p) & COMP_CODE_MASK) >> 24) +#define COMP_SUCCESS 1 +/* Data Buffer Error */ +#define COMP_DB_ERR 2 +/* Babble Detected Error */ +#define COMP_BABBLE 3 +/* USB Transaction Error */ +#define COMP_TX_ERR 4 +/* TRB Error - some TRB field is invalid */ +#define COMP_TRB_ERR 5 +/* Stall Error - USB device is stalled */ +#define COMP_STALL 6 +/* Resource Error - HC doesn't have memory for that device configuration */ +#define COMP_ENOMEM 7 +/* Bandwidth Error - not enough room in schedule for this dev config */ +#define COMP_BW_ERR 8 +/* No Slots Available Error - HC ran out of device slots */ +#define COMP_ENOSLOTS 9 +/* Invalid Stream Type Error */ +#define COMP_STREAM_ERR 10 +/* Slot Not Enabled Error - doorbell rung for disabled device slot */ +#define COMP_EBADSLT 11 +/* Endpoint Not Enabled Error */ +#define COMP_EBADEP 12 +/* Short Packet */ +#define COMP_SHORT_TX 13 +/* Ring Underrun - doorbell rung for an empty isoc OUT ep ring */ +#define COMP_UNDERRUN 14 +/* Ring Overrun - isoc IN ep ring is empty when ep is scheduled to RX */ +#define COMP_OVERRUN 15 +/* Virtual Function Event Ring Full Error */ +#define COMP_VF_FULL 16 +/* Parameter Error - Context parameter is invalid */ +#define COMP_EINVAL 17 +/* Bandwidth Overrun Error - isoc ep exceeded its allocated bandwidth */ +#define COMP_BW_OVER 18 +/* Context State Error - illegal context state transition requested */ +#define COMP_CTX_STATE 19 +/* No Ping Response Error - HC didn't get PING_RESPONSE in time to TX */ +#define COMP_PING_ERR 20 +/* Event Ring is full */ +#define COMP_ER_FULL 21 +/* Missed Service Error - HC couldn't service an isoc ep within interval */ +#define COMP_MISSED_INT 23 +/* Successfully stopped command ring */ +#define COMP_CMD_STOP 24 +/* Successfully aborted current command and stopped command ring */ +#define COMP_CMD_ABORT 25 +/* Stopped - transfer was terminated by a stop endpoint command */ +#define COMP_STOP 26 +/* Same as COMP_EP_STOPPED, but the transfered length in the event is invalid */ +#define COMP_STOP_INVAL 27 +/* Control Abort Error - Debug Capability - control pipe aborted */ +#define COMP_DBG_ABORT 28 +/* TRB type 29 and 30 reserved */ +/* Isoc Buffer Overrun - an isoc IN ep sent more data than could fit in TD */ +#define COMP_BUFF_OVER 31 +/* Event Lost Error - xHC has an "internal event overrun condition" */ +#define COMP_ISSUES 32 +/* Undefined Error - reported when other error codes don't apply */ +#define COMP_UNKNOWN 33 +/* Invalid Stream ID Error */ +#define COMP_STRID_ERR 34 +/* Secondary Bandwidth Error - may be returned by a Configure Endpoint cmd */ +/* FIXME - check for this */ +#define COMP_2ND_BW_ERR 35 +/* Split Transaction Error */ +#define COMP_SPLIT_ERR 36 + +struct xhci_link_trb { + /* 64-bit segment pointer*/ + u32 segment_ptr[2]; + u32 intr_target; + u32 control; +} __attribute__ ((packed)); + +/* control bitfields */ +#define LINK_TOGGLE (0x1<<1) + + +union xhci_trb { + struct xhci_link_trb link; + struct xhci_transfer_event trans_event; +}; + +/* Normal TRB fields */ +/* transfer_len bitmasks - bits 0:16 */ +#define TRB_LEN(p) ((p) & 0x1ffff) +/* TD size - number of bytes remaining in the TD (including this TRB): + * bits 17 - 21. Shift the number of bytes by 10. */ +#define TD_REMAINDER(p) ((((p) >> 10) & 0x1f) << 17) +/* Interrupter Target - which MSI-X vector to target the completion event at */ +#define TRB_INTR_TARGET(p) (((p) & 0x3ff) << 22) +#define GET_INTR_TARGET(p) (((p) >> 22) & 0x3ff) + +/* Cycle bit - indicates TRB ownership by HC or HCD */ +#define TRB_CYCLE (1<<0) +/* + * Force next event data TRB to be evaluated before task switch. + * Used to pass OS data back after a TD completes. + */ +#define TRB_ENT (1<<1) +/* Interrupt on short packet */ +#define TRB_ISP (1<<2) +/* Set PCIe no snoop attribute */ +#define TRB_NO_SNOOP (1<<3) +/* Chain multiple TRBs into a TD */ +#define TRB_CHAIN (1<<4) +/* Interrupt on completion */ +#define TRB_IOC (1<<5) +/* The buffer pointer contains immediate data */ +#define TRB_IDT (1<<6) + + +/* Control transfer TRB specific fields */ +#define TRB_DIR_IN (1<<16) + +/* TRB bit mask */ +#define TRB_TYPE_BITMASK (0xfc00) +#define TRB_TYPE(p) ((p) << 10) +/* TRB type IDs */ +/* bulk, interrupt, isoc scatter/gather, and control data stage */ +#define TRB_NORMAL 1 +/* setup stage for control transfers */ +#define TRB_SETUP 2 +/* data stage for control transfers */ +#define TRB_DATA 3 +/* status stage for control transfers */ +#define TRB_STATUS 4 +/* isoc transfers */ +#define TRB_ISOC 5 +/* TRB for linking ring segments */ +#define TRB_LINK 6 +#define TRB_EVENT_DATA 7 +/* Transfer Ring No-op (not for the command ring) */ +#define TRB_TR_NOOP 8 +/* Command TRBs */ +/* Enable Slot Command */ +#define TRB_ENABLE_SLOT 9 +/* Disable Slot Command */ +#define TRB_DISABLE_SLOT 10 +/* Address Device Command */ +#define TRB_ADDR_DEV 11 +/* Configure Endpoint Command */ +#define TRB_CONFIG_EP 12 +/* Evaluate Context Command */ +#define TRB_EVAL_CONTEXT 13 +/* Reset Transfer Ring Command */ +#define TRB_RESET_RING 14 +/* Stop Transfer Ring Command */ +#define TRB_STOP_RING 15 +/* Set Transfer Ring Dequeue Pointer Command */ +#define TRB_SET_DEQ 16 +/* Reset Device Command */ +#define TRB_RESET_DEV 17 +/* Force Event Command (opt) */ +#define TRB_FORCE_EVENT 18 +/* Negotiate Bandwidth Command (opt) */ +#define TRB_NEG_BANDWIDTH 19 +/* Set Latency Tolerance Value Command (opt) */ +#define TRB_SET_LT 20 +/* Get port bandwidth Command */ +#define TRB_GET_BW 21 +/* Force Header Command - generate a transaction or link management packet */ +#define TRB_FORCE_HEADER 22 +/* No-op Command - not for transfer rings */ +#define TRB_CMD_NOOP 23 +/* TRB IDs 24-31 reserved */ +/* Event TRBS */ +/* Transfer Event */ +#define TRB_TRANSFER 32 +/* Command Completion Event */ +#define TRB_COMPLETION 33 +/* Port Status Change Event */ +#define TRB_PORT_STATUS 34 +/* Bandwidth Request Event (opt) */ +#define TRB_BANDWIDTH_EVENT 35 +/* Doorbell Event (opt) */ +#define TRB_DOORBELL 36 +/* Host Controller Event */ +#define TRB_HC_EVENT 37 +/* Device Notification Event - device sent function wake notification */ +#define TRB_DEV_NOTE 38 +/* MFINDEX Wrap Event - microframe counter wrapped */ +#define TRB_MFINDEX_WRAP 39 +/* TRB IDs 40-47 reserved, 48-63 is vendor-defined */ + +/* + * TRBS_PER_SEGMENT must be a multiple of 4, + * since the command ring is 64-byte aligned. + * It must also be greater than 16. + */ +#define TRBS_PER_SEGMENT 64 +#define SEGMENT_SIZE (TRBS_PER_SEGMENT*16) + +struct xhci_segment { + union xhci_trb *trbs; + /* private to HCD */ + struct xhci_segment *next; + dma_addr_t dma; +} __attribute__ ((packed)); + +struct xhci_ring { + struct xhci_segment *first_seg; + union xhci_trb *enqueue; + union xhci_trb *dequeue; + /* + * Write the cycle state into the TRB cycle field to give ownership of + * the TRB to the host controller (if we are the producer), or to check + * if we own the TRB (if we are the consumer). See section 4.9.1. + */ + u32 cycle_state; +}; + +struct xhci_erst_entry { + /* 64-bit event ring segment address */ + u32 seg_addr[2]; + u32 seg_size; + /* Set to zero */ + u32 rsvd; +} __attribute__ ((packed)); + +struct xhci_erst { + struct xhci_erst_entry *entries; + unsigned int num_entries; + /* xhci->event_ring keeps track of segment dma addresses */ + dma_addr_t erst_dma_addr; + /* Num entries the ERST can contain */ + unsigned int erst_size; +}; + +/* + * Each segment table entry is 4*32bits long. 1K seems like an ok size: + * (1K bytes * 8bytes/bit) / (4*32 bits) = 64 segment entries in the table, + * meaning 64 ring segments. + * Initial allocated size of the ERST, in number of entries */ +#define ERST_NUM_SEGS 1 +/* Initial allocated size of the ERST, in number of entries */ +#define ERST_SIZE 64 +/* Initial number of event segment rings allocated */ +#define ERST_ENTRIES 1 +/* XXX: Make these module parameters */ + /* There is one ehci_hci structure per controller */ struct xhci_hcd { @@ -414,6 +696,7 @@ struct xhci_hcd { struct xhci_cap_regs __iomem *cap_regs; struct xhci_op_regs __iomem *op_regs; struct xhci_run_regs __iomem *run_regs; + struct xhci_doorbell_array __iomem *dba; /* Our HCD's current interrupter register set */ struct intr_reg __iomem *ir_set; @@ -441,6 +724,14 @@ struct xhci_hcd { /* only one MSI vector for now, but might need more later */ int msix_count; struct msix_entry *msix_entries; + /* data structures */ + struct xhci_ring *cmd_ring; + struct xhci_ring *event_ring; + struct xhci_erst erst; + + /* DMA pools */ + struct dma_pool *device_pool; + struct dma_pool *segment_pool; }; /* convert between an HCD pointer and the corresponding EHCI_HCD */ @@ -488,6 +779,11 @@ static inline void xhci_writel(const struct xhci_hcd *xhci, /* xHCI debugging */ void xhci_print_ir_set(struct xhci_hcd *xhci, struct intr_reg *ir_set, int set_num); void xhci_print_registers(struct xhci_hcd *xhci); +void xhci_dbg_regs(struct xhci_hcd *xhci); +void xhci_print_run_regs(struct xhci_hcd *xhci); +void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring); +void xhci_dbg_erst(struct xhci_hcd *xhci, struct xhci_erst *erst); +void xhci_dbg_cmd_ptrs(struct xhci_hcd *xhci); /* xHCI memory managment */ void xhci_mem_cleanup(struct xhci_hcd *xhci); -- cgit v1.2.3 From a74588f94655263b96dacbbf14aac0958d8b7409 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:53:42 -0700 Subject: USB: xhci: Device context array allocation. Instead of keeping a "frame list" like older host controllers, the xHCI host controller keeps internal representations of the USB devices, with a transfer ring per endpoint. The host controller queues Transfer Request Blocks (TRBs) to the endpoint ring, and then "rings the doorbell" for that device. The host controller processes the transfer, places a transfer completion event on the event ring, and interrupts the system. The device context base address array must be allocated by the xHCI host controller driver, along with the device contexts it points to. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-mem.c | 21 ++++++ drivers/usb/host/xhci.h | 175 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 7cf15ca854be..be5a05b2021c 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -220,6 +220,12 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) dma_pool_destroy(xhci->segment_pool); xhci->segment_pool = NULL; xhci_dbg(xhci, "Freed segment pool\n"); + xhci_writel(xhci, 0, &xhci->op_regs->dcbaa_ptr[1]); + xhci_writel(xhci, 0, &xhci->op_regs->dcbaa_ptr[0]); + if (xhci->dcbaa) + pci_free_consistent(pdev, sizeof(*xhci->dcbaa), + xhci->dcbaa, xhci->dcbaa->dma); + xhci->dcbaa = NULL; xhci->page_size = 0; xhci->page_shift = 0; } @@ -262,6 +268,21 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) (unsigned int) val); xhci_writel(xhci, val, &xhci->op_regs->config_reg); + /* + * Section 5.4.8 - doorbell array must be + * "physically contiguous and 64-byte (cache line) aligned". + */ + xhci->dcbaa = pci_alloc_consistent(to_pci_dev(dev), + sizeof(*xhci->dcbaa), &dma); + if (!xhci->dcbaa) + goto fail; + memset(xhci->dcbaa, 0, sizeof *(xhci->dcbaa)); + xhci->dcbaa->dma = dma; + xhci_dbg(xhci, "// Setting device context base array address to 0x%x\n", + xhci->dcbaa->dma); + xhci_writel(xhci, (u32) 0, &xhci->op_regs->dcbaa_ptr[1]); + xhci_writel(xhci, dma, &xhci->op_regs->dcbaa_ptr[0]); + /* * Initialize the ring segment pool. The ring must be a contiguous * structure comprised of TRBs. The TRBs must be 16 byte aligned, diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index ed331310f1a8..f168fcac5999 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -436,6 +436,180 @@ struct xhci_doorbell_array { #define DB_MASK (0xff << 8) +/** + * struct xhci_slot_ctx + * @dev_info: Route string, device speed, hub info, and last valid endpoint + * @dev_info2: Max exit latency for device number, root hub port number + * @tt_info: tt_info is used to construct split transaction tokens + * @dev_state: slot state and device address + * + * Slot Context - section 6.2.1.1. This assumes the HC uses 32-byte context + * structures. If the HC uses 64-byte contexts, there is an additional 32 bytes + * reserved at the end of the slot context for HC internal use. + */ +struct xhci_slot_ctx { + u32 dev_info; + u32 dev_info2; + u32 tt_info; + u32 dev_state; + /* offset 0x10 to 0x1f reserved for HC internal use */ + u32 reserved[4]; +} __attribute__ ((packed)); + +/* dev_info bitmasks */ +/* Route String - 0:19 */ +#define ROUTE_STRING_MASK (0xfffff) +/* Device speed - values defined by PORTSC Device Speed field - 20:23 */ +#define DEV_SPEED (0xf << 20) +/* bit 24 reserved */ +/* Is this LS/FS device connected through a HS hub? - bit 25 */ +#define DEV_MTT (0x1 << 25) +/* Set if the device is a hub - bit 26 */ +#define DEV_HUB (0x1 << 26) +/* Index of the last valid endpoint context in this device context - 27:31 */ +#define LAST_EP_MASK (0x1f << 27) +#define LAST_EP(p) ((p) << 27) + +/* dev_info2 bitmasks */ +/* Max Exit Latency (ms) - worst case time to wake up all links in dev path */ +#define MAX_EXIT (0xffff) +/* Root hub port number that is needed to access the USB device */ +#define ROOT_HUB_PORT (0xff << 16) + +/* tt_info bitmasks */ +/* + * TT Hub Slot ID - for low or full speed devices attached to a high-speed hub + * The Slot ID of the hub that isolates the high speed signaling from + * this low or full-speed device. '0' if attached to root hub port. + */ +#define TT_SLOT (0xff) +/* + * The number of the downstream facing port of the high-speed hub + * '0' if the device is not low or full speed. + */ +#define TT_PORT (0xff << 8) + +/* dev_state bitmasks */ +/* USB device address - assigned by the HC */ +#define DEV_ADDR (0xff) +/* bits 8:26 reserved */ +/* Slot state */ +#define SLOT_STATE (0x1f << 27) + + +/** + * struct xhci_ep_ctx + * @ep_info: endpoint state, streams, mult, and interval information. + * @ep_info2: information on endpoint type, max packet size, max burst size, + * error count, and whether the HC will force an event for all + * transactions. + * @ep_ring: 64-bit ring address. If the endpoint only defines one flow, + * this points to the endpoint transfer ring. Otherwise, it points + * to a flow context array, which has a ring pointer for each flow. + * @intr_target: + * 64-bit address of the Interrupter Target that will receive + * events from this endpoint. + * + * Endpoint Context - section 6.2.1.2. This assumes the HC uses 32-byte context + * structures. If the HC uses 64-byte contexts, there is an additional 32 bytes + * reserved at the end of the endpoint context for HC internal use. + */ +struct xhci_ep_ctx { + u32 ep_info; + u32 ep_info2; + /* 64-bit endpoint ring address */ + u32 ep_ring[2]; + /* 64-bit address of the interrupter target */ + u32 intr_target[2]; + /* offset 0x14 - 0x1f reserved for HC internal use */ + u32 reserved[2]; +} __attribute__ ((packed)); + +/* ep_info bitmasks */ +/* + * Endpoint State - bits 0:2 + * 0 - disabled + * 1 - running + * 2 - halted due to halt condition - ok to manipulate endpoint ring + * 3 - stopped + * 4 - TRB error + * 5-7 - reserved + */ +#define EP_STATE (0xf) +/* Mult - Max number of burtst within an interval, in EP companion desc. */ +#define EP_MULT(p) ((p & 0x3) << 8) +/* bits 10:14 are Max Primary Streams */ +/* bit 15 is Linear Stream Array */ +/* Interval - period between requests to an endpoint - 125u increments. */ +#define EP_INTERVAL (0xff << 16) + +/* ep_info2 bitmasks */ +/* + * Force Event - generate transfer events for all TRBs for this endpoint + * This will tell the HC to ignore the IOC and ISP flags (for debugging only). + */ +#define FORCE_EVENT (0x1) +#define ERROR_COUNT(p) (((p) & 0x3) << 1) +#define EP_TYPE(p) ((p) << 3) +#define ISOC_OUT_EP 1 +#define BULK_OUT_EP 2 +#define INT_OUT_EP 3 +#define CTRL_EP 4 +#define ISOC_IN_EP 5 +#define BULK_IN_EP 6 +#define INT_IN_EP 7 +/* bit 6 reserved */ +/* bit 7 is Host Initiate Disable - for disabling stream selection */ +#define MAX_BURST(p) (((p)&0xff) << 8) +#define MAX_PACKET(p) (((p)&0xffff) << 16) + + +/** + * struct xhci_device_control + * Input/Output context; see section 6.2.5. + * + * @drop_context: set the bit of the endpoint context you want to disable + * @add_context: set the bit of the endpoint context you want to enable + */ +struct xhci_device_control { + u32 drop_flags; + u32 add_flags; + u32 rsvd[6]; + struct xhci_slot_ctx slot; + struct xhci_ep_ctx ep[31]; +} __attribute__ ((packed)); + +/* drop context bitmasks */ +#define DROP_EP(x) (0x1 << x) +/* add context bitmasks */ +#define ADD_EP(x) (0x1 << x) + + +/** + * struct xhci_device_context_array + * @dev_context_ptr array of 64-bit DMA addresses for device contexts + */ +struct xhci_device_context_array { + /* 64-bit device addresses; we only write 32-bit addresses */ + u32 dev_context_ptrs[2*MAX_HC_SLOTS]; + /* private xHCD pointers */ + dma_addr_t dma; +} __attribute__ ((packed)); +/* TODO: write function to set the 64-bit device DMA address */ +/* + * TODO: change this to be dynamically sized at HC mem init time since the HC + * might not be able to handle the maximum number of devices possible. + */ + + +struct xhci_stream_ctx { + /* 64-bit stream ring address, cycle state, and stream type */ + u32 stream_ring[2]; + /* offset 0x14 - 0x1f reserved for HC internal use */ + u32 reserved[2]; +} __attribute__ ((packed)); + + struct xhci_transfer_event { /* 64-bit buffer address, or immediate data */ u32 buffer[2]; @@ -725,6 +899,7 @@ struct xhci_hcd { int msix_count; struct msix_entry *msix_entries; /* data structures */ + struct xhci_device_context_array *dcbaa; struct xhci_ring *cmd_ring; struct xhci_ring *event_ring; struct xhci_erst erst; -- cgit v1.2.3 From 7f84eef0dafb1d318263d8b71c38700aaf2d530d Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:53:56 -0700 Subject: USB: xhci: No-op command queueing and irq handler. xHCI host controllers can optionally implement a no-op test. This simple test ensures the OS has correctly setup all basic data structures and can correctly respond to interrupts from the host controller hardware. There are two rings exercised by the no-op test: the command ring, and the event ring. The host controller driver writes a no-op command TRB to the command ring, and rings the doorbell for the command ring (the first entry in the doorbell array). The hardware receives this event, places a command completion event on the event ring, and fires an interrupt. The host controller driver sees the interrupt, and checks the event ring for TRBs it can process, and sees the command completion event. (See the rules in xhci-ring.c for who "owns" a TRB. This is a simplified set of rules, and may not contain all the details that are in the xHCI 0.95 spec.) A timer fires every 60 seconds to debug the state of the hardware and command and event rings. This timer only runs if CONFIG_USB_XHCI_HCD_DEBUGGING is 'y'. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-dbg.c | 76 +++++++++ drivers/usb/host/xhci-hcd.c | 152 +++++++++++++++++- drivers/usb/host/xhci-mem.c | 11 +- drivers/usb/host/xhci-pci.c | 1 + drivers/usb/host/xhci-ring.c | 367 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci.h | 53 ++++++- 6 files changed, 647 insertions(+), 13 deletions(-) create mode 100644 drivers/usb/host/xhci-ring.c (limited to 'drivers') diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 5724683cef16..6dbf7d856f80 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -230,6 +230,64 @@ void xhci_print_registers(struct xhci_hcd *xhci) xhci_print_op_regs(xhci); } +void xhci_print_trb_offsets(struct xhci_hcd *xhci, union xhci_trb *trb) +{ + int i; + for (i = 0; i < 4; ++i) + xhci_dbg(xhci, "Offset 0x%x = 0x%x\n", + i*4, trb->generic.field[i]); +} + +/** + * Debug a transfer request block (TRB). + */ +void xhci_debug_trb(struct xhci_hcd *xhci, union xhci_trb *trb) +{ + u64 address; + u32 type = xhci_readl(xhci, &trb->link.control) & TRB_TYPE_BITMASK; + + switch (type) { + case TRB_TYPE(TRB_LINK): + xhci_dbg(xhci, "Link TRB:\n"); + xhci_print_trb_offsets(xhci, trb); + + address = trb->link.segment_ptr[0] + + (((u64) trb->link.segment_ptr[1]) << 32); + xhci_dbg(xhci, "Next ring segment DMA address = 0x%llx\n", address); + + xhci_dbg(xhci, "Interrupter target = 0x%x\n", + GET_INTR_TARGET(trb->link.intr_target)); + xhci_dbg(xhci, "Cycle bit = %u\n", + (unsigned int) (trb->link.control & TRB_CYCLE)); + xhci_dbg(xhci, "Toggle cycle bit = %u\n", + (unsigned int) (trb->link.control & LINK_TOGGLE)); + xhci_dbg(xhci, "No Snoop bit = %u\n", + (unsigned int) (trb->link.control & TRB_NO_SNOOP)); + break; + case TRB_TYPE(TRB_TRANSFER): + address = trb->trans_event.buffer[0] + + (((u64) trb->trans_event.buffer[1]) << 32); + /* + * FIXME: look at flags to figure out if it's an address or if + * the data is directly in the buffer field. + */ + xhci_dbg(xhci, "DMA address or buffer contents= %llu\n", address); + break; + case TRB_TYPE(TRB_COMPLETION): + address = trb->event_cmd.cmd_trb[0] + + (((u64) trb->event_cmd.cmd_trb[1]) << 32); + xhci_dbg(xhci, "Command TRB pointer = %llu\n", address); + xhci_dbg(xhci, "Completion status = %u\n", + (unsigned int) GET_COMP_CODE(trb->event_cmd.status)); + xhci_dbg(xhci, "Flags = 0x%x\n", (unsigned int) trb->event_cmd.flags); + break; + default: + xhci_dbg(xhci, "Unknown TRB with TRB type ID %u\n", + (unsigned int) type>>10); + xhci_print_trb_offsets(xhci, trb); + break; + } +} /** * Debug a segment with an xHCI ring. @@ -261,6 +319,20 @@ void xhci_debug_segment(struct xhci_hcd *xhci, struct xhci_segment *seg) } } +void xhci_dbg_ring_ptrs(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + xhci_dbg(xhci, "Ring deq = 0x%x (virt), 0x%x (dma)\n", + (unsigned int) ring->dequeue, + trb_virt_to_dma(ring->deq_seg, ring->dequeue)); + xhci_dbg(xhci, "Ring deq updated %u times\n", + ring->deq_updates); + xhci_dbg(xhci, "Ring enq = 0x%x (virt), 0x%x (dma)\n", + (unsigned int) ring->enqueue, + trb_virt_to_dma(ring->enq_seg, ring->enqueue)); + xhci_dbg(xhci, "Ring enq updated %u times\n", + ring->enq_updates); +} + /** * Debugging for an xHCI ring, which is a queue broken into multiple segments. * @@ -277,6 +349,10 @@ void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring) struct xhci_segment *first_seg = ring->first_seg; xhci_debug_segment(xhci, first_seg); + if (!ring->enq_updates && !ring->deq_updates) { + xhci_dbg(xhci, " Ring has not been updated\n"); + return; + } for (seg = first_seg->next; seg != first_seg; seg = seg->next) xhci_debug_segment(xhci, seg); } diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 011f47810665..a99c119e9fd9 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -217,6 +217,120 @@ int xhci_init(struct usb_hcd *hcd) return retval; } +/* + * Called in interrupt context when there might be work + * queued on the event ring + * + * xhci->lock must be held by caller. + */ +static void xhci_work(struct xhci_hcd *xhci) +{ + u32 temp; + + /* + * Clear the op reg interrupt status first, + * so we can receive interrupts from other MSI-X interrupters. + * Write 1 to clear the interrupt status. + */ + temp = xhci_readl(xhci, &xhci->op_regs->status); + temp |= STS_EINT; + xhci_writel(xhci, temp, &xhci->op_regs->status); + /* FIXME when MSI-X is supported and there are multiple vectors */ + /* Clear the MSI-X event interrupt status */ + + /* Acknowledge the interrupt */ + temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); + temp |= 0x3; + xhci_writel(xhci, temp, &xhci->ir_set->irq_pending); + /* Flush posted writes */ + xhci_readl(xhci, &xhci->ir_set->irq_pending); + + /* FIXME this should be a delayed service routine that clears the EHB */ + handle_event(xhci); + + /* Clear the event handler busy flag; the event ring should be empty. */ + temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[0]); + xhci_writel(xhci, temp & ~ERST_EHB, &xhci->ir_set->erst_dequeue[0]); + /* Flush posted writes -- FIXME is this necessary? */ + xhci_readl(xhci, &xhci->ir_set->irq_pending); +} + +/*-------------------------------------------------------------------------*/ + +/* + * xHCI spec says we can get an interrupt, and if the HC has an error condition, + * we might get bad data out of the event ring. Section 4.10.2.7 has a list of + * indicators of an event TRB error, but we check the status *first* to be safe. + */ +irqreturn_t xhci_irq(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + u32 temp, temp2; + + spin_lock(&xhci->lock); + /* Check if the xHC generated the interrupt, or the irq is shared */ + temp = xhci_readl(xhci, &xhci->op_regs->status); + temp2 = xhci_readl(xhci, &xhci->ir_set->irq_pending); + if (!(temp & STS_EINT) && !ER_IRQ_PENDING(temp2)) { + spin_unlock(&xhci->lock); + return IRQ_NONE; + } + + temp = xhci_readl(xhci, &xhci->op_regs->status); + if (temp & STS_FATAL) { + xhci_warn(xhci, "WARNING: Host System Error\n"); + xhci_halt(xhci); + xhci_to_hcd(xhci)->state = HC_STATE_HALT; + return -ESHUTDOWN; + } + + xhci_work(xhci); + spin_unlock(&xhci->lock); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING +void event_ring_work(unsigned long arg) +{ + unsigned long flags; + int temp; + struct xhci_hcd *xhci = (struct xhci_hcd *) arg; + int i, j; + + xhci_dbg(xhci, "Poll event ring: %lu\n", jiffies); + + spin_lock_irqsave(&xhci->lock, flags); + temp = xhci_readl(xhci, &xhci->op_regs->status); + xhci_dbg(xhci, "op reg status = 0x%x\n", temp); + temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); + xhci_dbg(xhci, "ir_set 0 pending = 0x%x\n", temp); + xhci_dbg(xhci, "No-op commands handled = %d\n", xhci->noops_handled); + xhci_dbg(xhci, "HC error bitmask = 0x%x\n", xhci->error_bitmask); + xhci->error_bitmask = 0; + xhci_dbg(xhci, "Event ring:\n"); + xhci_debug_segment(xhci, xhci->event_ring->deq_seg); + xhci_dbg_ring_ptrs(xhci, xhci->event_ring); + temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[0]); + temp &= ERST_PTR_MASK; + xhci_dbg(xhci, "ERST deq = 0x%x\n", temp); + xhci_dbg(xhci, "Command ring:\n"); + xhci_debug_segment(xhci, xhci->cmd_ring->deq_seg); + xhci_dbg_ring_ptrs(xhci, xhci->cmd_ring); + xhci_dbg_cmd_ptrs(xhci); + + if (xhci->noops_submitted != NUM_TEST_NOOPS) + if (setup_one_noop(xhci)) + ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + if (!xhci->zombie) + mod_timer(&xhci->event_ring_timer, jiffies + POLL_TIMEOUT * HZ); + else + xhci_dbg(xhci, "Quit polling the event ring.\n"); +} +#endif + /* * Start the HC after it was halted. * @@ -233,8 +347,9 @@ int xhci_run(struct usb_hcd *hcd) { u32 temp; struct xhci_hcd *xhci = hcd_to_xhci(hcd); - xhci_dbg(xhci, "xhci_run\n"); + void (*doorbell)(struct xhci_hcd *) = NULL; + xhci_dbg(xhci, "xhci_run\n"); #if 0 /* FIXME: MSI not setup yet */ /* Do this at the very last minute */ ret = xhci_setup_msix(xhci); @@ -243,6 +358,17 @@ int xhci_run(struct usb_hcd *hcd) return -ENOSYS; #endif +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING + init_timer(&xhci->event_ring_timer); + xhci->event_ring_timer.data = (unsigned long) xhci; + xhci->event_ring_timer.function = event_ring_work; + /* Poll the event ring */ + xhci->event_ring_timer.expires = jiffies + POLL_TIMEOUT * HZ; + xhci->zombie = 0; + xhci_dbg(xhci, "Setting event ring polling timer\n"); + add_timer(&xhci->event_ring_timer); +#endif + xhci_dbg(xhci, "// Set the interrupt modulation register\n"); temp = xhci_readl(xhci, &xhci->ir_set->irq_control); temp &= 0xffff; @@ -266,10 +392,24 @@ int xhci_run(struct usb_hcd *hcd) &xhci->ir_set->irq_pending); xhci_print_ir_set(xhci, xhci->ir_set, 0); + if (NUM_TEST_NOOPS > 0) + doorbell = setup_one_noop(xhci); + xhci_dbg(xhci, "Command ring memory map follows:\n"); xhci_debug_ring(xhci, xhci->cmd_ring); + xhci_dbg_ring_ptrs(xhci, xhci->cmd_ring); + xhci_dbg_cmd_ptrs(xhci); + xhci_dbg(xhci, "ERST memory map follows:\n"); xhci_dbg_erst(xhci, &xhci->erst); + xhci_dbg(xhci, "Event ring:\n"); + xhci_debug_ring(xhci, xhci->event_ring); + xhci_dbg_ring_ptrs(xhci, xhci->event_ring); + temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[1]); + xhci_dbg(xhci, "ERST deq upper = 0x%x\n", temp); + temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[0]); + temp &= ERST_PTR_MASK; + xhci_dbg(xhci, "ERST deq = 0x%x\n", temp); temp = xhci_readl(xhci, &xhci->op_regs->command); temp |= (CMD_RUN); @@ -280,6 +420,8 @@ int xhci_run(struct usb_hcd *hcd) temp = xhci_readl(xhci, &xhci->op_regs->command); xhci_dbg(xhci, "// @%x = 0x%x\n", (unsigned int) &xhci->op_regs->command, temp); + if (doorbell) + (*doorbell)(xhci); xhci_dbg(xhci, "Finished xhci_run\n"); return 0; @@ -309,6 +451,12 @@ void xhci_stop(struct usb_hcd *hcd) #if 0 /* No MSI yet */ xhci_cleanup_msix(xhci); #endif +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING + /* Tell the event ring poll function not to reschedule */ + xhci->zombie = 1; + del_timer_sync(&xhci->event_ring_timer); +#endif + xhci_dbg(xhci, "// Disabling event ring interrupts\n"); temp = xhci_readl(xhci, &xhci->op_regs->status); xhci_writel(xhci, temp & ~STS_EINT, &xhci->op_regs->status); @@ -346,6 +494,8 @@ void xhci_shutdown(struct usb_hcd *hcd) xhci_readl(xhci, &xhci->op_regs->status)); } +/*-------------------------------------------------------------------------*/ + int xhci_get_frame(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index be5a05b2021c..005d44641d81 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -172,7 +172,9 @@ static struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, } /* The ring is empty, so the enqueue pointer == dequeue pointer */ ring->enqueue = ring->first_seg->trbs; + ring->enq_seg = ring->first_seg; ring->dequeue = ring->enqueue; + ring->deq_seg = ring->first_seg; /* The ring is initialized to 0. The producer must write 1 to the cycle * bit to handover ownership of the TRB, so PCS = 1. The consumer must * compare CCS to the cycle bit to check ownership, so CCS = 1. @@ -374,14 +376,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci_writel(xhci, val, &xhci->ir_set->erst_base[0]); /* Set the event ring dequeue address */ - xhci_dbg(xhci, "// Set ERST dequeue address for ir_set 0 = 0x%x%x\n", - xhci->erst.entries[0].seg_addr[1], xhci->erst.entries[0].seg_addr[0]); - val = xhci_readl(xhci, &xhci->run_regs->ir_set[0].erst_dequeue[0]); - val &= ERST_PTR_MASK; - val |= (xhci->erst.entries[0].seg_addr[0] & ~ERST_PTR_MASK); - xhci_writel(xhci, val, &xhci->run_regs->ir_set[0].erst_dequeue[0]); - xhci_writel(xhci, xhci->erst.entries[0].seg_addr[1], - &xhci->run_regs->ir_set[0].erst_dequeue[1]); + set_hc_event_deq(xhci); xhci_dbg(xhci, "Wrote ERST address to ir_set 0.\n"); xhci_print_ir_set(xhci, xhci->ir_set, 0); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 4015082adf60..89614af80d20 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -96,6 +96,7 @@ static const struct hc_driver xhci_pci_hc_driver = { /* * generic hardware linkage */ + .irq = xhci_irq, .flags = HCD_MEMORY | HCD_USB3, /* diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c new file mode 100644 index 000000000000..c7e3c7142b9d --- /dev/null +++ b/drivers/usb/host/xhci-ring.c @@ -0,0 +1,367 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Ring initialization rules: + * 1. Each segment is initialized to zero, except for link TRBs. + * 2. Ring cycle state = 0. This represents Producer Cycle State (PCS) or + * Consumer Cycle State (CCS), depending on ring function. + * 3. Enqueue pointer = dequeue pointer = address of first TRB in the segment. + * + * Ring behavior rules: + * 1. A ring is empty if enqueue == dequeue. This means there will always be at + * least one free TRB in the ring. This is useful if you want to turn that + * into a link TRB and expand the ring. + * 2. When incrementing an enqueue or dequeue pointer, if the next TRB is a + * link TRB, then load the pointer with the address in the link TRB. If the + * link TRB had its toggle bit set, you may need to update the ring cycle + * state (see cycle bit rules). You may have to do this multiple times + * until you reach a non-link TRB. + * 3. A ring is full if enqueue++ (for the definition of increment above) + * equals the dequeue pointer. + * + * Cycle bit rules: + * 1. When a consumer increments a dequeue pointer and encounters a toggle bit + * in a link TRB, it must toggle the ring cycle state. + * 2. When a producer increments an enqueue pointer and encounters a toggle bit + * in a link TRB, it must toggle the ring cycle state. + * + * Producer rules: + * 1. Check if ring is full before you enqueue. + * 2. Write the ring cycle state to the cycle bit in the TRB you're enqueuing. + * Update enqueue pointer between each write (which may update the ring + * cycle state). + * 3. Notify consumer. If SW is producer, it rings the doorbell for command + * and endpoint rings. If HC is the producer for the event ring, + * and it generates an interrupt according to interrupt modulation rules. + * + * Consumer rules: + * 1. Check if TRB belongs to you. If the cycle bit == your ring cycle state, + * the TRB is owned by the consumer. + * 2. Update dequeue pointer (which may update the ring cycle state) and + * continue processing TRBs until you reach a TRB which is not owned by you. + * 3. Notify the producer. SW is the consumer for the event ring, and it + * updates event ring dequeue pointer. HC is the consumer for the command and + * endpoint rings; it generates events on the event ring for these. + */ + +#include "xhci.h" + +/* + * Returns zero if the TRB isn't in this segment, otherwise it returns the DMA + * address of the TRB. + */ +dma_addr_t trb_virt_to_dma(struct xhci_segment *seg, + union xhci_trb *trb) +{ + unsigned int offset; + + if (!seg || !trb || (void *) trb < (void *) seg->trbs) + return 0; + /* offset in bytes, since these are byte-addressable */ + offset = (unsigned int) trb - (unsigned int) seg->trbs; + /* SEGMENT_SIZE in bytes, trbs are 16-byte aligned */ + if (offset > SEGMENT_SIZE || (offset % sizeof(*trb)) != 0) + return 0; + return seg->dma + offset; +} + +/* Does this link TRB point to the first segment in a ring, + * or was the previous TRB the last TRB on the last segment in the ERST? + */ +static inline bool last_trb_on_last_seg(struct xhci_hcd *xhci, struct xhci_ring *ring, + struct xhci_segment *seg, union xhci_trb *trb) +{ + if (ring == xhci->event_ring) + return (trb == &seg->trbs[TRBS_PER_SEGMENT]) && + (seg->next == xhci->event_ring->first_seg); + else + return trb->link.control & LINK_TOGGLE; +} + +/* Is this TRB a link TRB or was the last TRB the last TRB in this event ring + * segment? I.e. would the updated event TRB pointer step off the end of the + * event seg? + */ +static inline int last_trb(struct xhci_hcd *xhci, struct xhci_ring *ring, + struct xhci_segment *seg, union xhci_trb *trb) +{ + if (ring == xhci->event_ring) + return trb == &seg->trbs[TRBS_PER_SEGMENT]; + else + return (trb->link.control & TRB_TYPE_BITMASK) == TRB_TYPE(TRB_LINK); +} + +/* + * See Cycle bit rules. SW is the consumer for the event ring only. + * Don't make a ring full of link TRBs. That would be dumb and this would loop. + */ +static void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring, bool consumer) +{ + union xhci_trb *next = ++(ring->dequeue); + + ring->deq_updates++; + /* Update the dequeue pointer further if that was a link TRB or we're at + * the end of an event ring segment (which doesn't have link TRBS) + */ + while (last_trb(xhci, ring, ring->deq_seg, next)) { + if (consumer && last_trb_on_last_seg(xhci, ring, ring->deq_seg, next)) { + ring->cycle_state = (ring->cycle_state ? 0 : 1); + if (!in_interrupt()) + xhci_dbg(xhci, "Toggle cycle state for ring 0x%x = %i\n", + (unsigned int) ring, + (unsigned int) ring->cycle_state); + } + ring->deq_seg = ring->deq_seg->next; + ring->dequeue = ring->deq_seg->trbs; + next = ring->dequeue; + } +} + +/* + * See Cycle bit rules. SW is the consumer for the event ring only. + * Don't make a ring full of link TRBs. That would be dumb and this would loop. + * + * If we've just enqueued a TRB that is in the middle of a TD (meaning the + * chain bit is set), then set the chain bit in all the following link TRBs. + * If we've enqueued the last TRB in a TD, make sure the following link TRBs + * have their chain bit cleared (so that each Link TRB is a separate TD). + * + * Section 6.4.4.1 of the 0.95 spec says link TRBs cannot have the chain bit + * set, but other sections talk about dealing with the chain bit set. + * Assume section 6.4.4.1 is wrong, and the chain bit can be set in a Link TRB. + */ +static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, bool consumer) +{ + u32 chain; + union xhci_trb *next; + + chain = ring->enqueue->generic.field[3] & TRB_CHAIN; + next = ++(ring->enqueue); + + ring->enq_updates++; + /* Update the dequeue pointer further if that was a link TRB or we're at + * the end of an event ring segment (which doesn't have link TRBS) + */ + while (last_trb(xhci, ring, ring->enq_seg, next)) { + if (!consumer) { + if (ring != xhci->event_ring) { + /* Give this link TRB to the hardware */ + if (next->link.control & TRB_CYCLE) + next->link.control &= (u32) ~TRB_CYCLE; + else + next->link.control |= (u32) TRB_CYCLE; + next->link.control &= TRB_CHAIN; + next->link.control |= chain; + } + /* Toggle the cycle bit after the last ring segment. */ + if (last_trb_on_last_seg(xhci, ring, ring->enq_seg, next)) { + ring->cycle_state = (ring->cycle_state ? 0 : 1); + if (!in_interrupt()) + xhci_dbg(xhci, "Toggle cycle state for ring 0x%x = %i\n", + (unsigned int) ring, + (unsigned int) ring->cycle_state); + } + } + ring->enq_seg = ring->enq_seg->next; + ring->enqueue = ring->enq_seg->trbs; + next = ring->enqueue; + } +} + +/* + * Check to see if there's room to enqueue num_trbs on the ring. See rules + * above. + * FIXME: this would be simpler and faster if we just kept track of the number + * of free TRBs in a ring. + */ +static int room_on_ring(struct xhci_hcd *xhci, struct xhci_ring *ring, + unsigned int num_trbs) +{ + int i; + union xhci_trb *enq = ring->enqueue; + struct xhci_segment *enq_seg = ring->enq_seg; + + /* Check if ring is empty */ + if (enq == ring->dequeue) + return 1; + /* Make sure there's an extra empty TRB available */ + for (i = 0; i <= num_trbs; ++i) { + if (enq == ring->dequeue) + return 0; + enq++; + while (last_trb(xhci, ring, enq_seg, enq)) { + enq_seg = enq_seg->next; + enq = enq_seg->trbs; + } + } + return 1; +} + +void set_hc_event_deq(struct xhci_hcd *xhci) +{ + u32 temp; + dma_addr_t deq; + + deq = trb_virt_to_dma(xhci->event_ring->deq_seg, + xhci->event_ring->dequeue); + if (deq == 0 && !in_interrupt()) + xhci_warn(xhci, "WARN something wrong with SW event ring " + "dequeue ptr.\n"); + /* Update HC event ring dequeue pointer */ + temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[0]); + temp &= ERST_PTR_MASK; + if (!in_interrupt()) + xhci_dbg(xhci, "// Write event ring dequeue pointer\n"); + xhci_writel(xhci, 0, &xhci->ir_set->erst_dequeue[1]); + xhci_writel(xhci, (deq & ~ERST_PTR_MASK) | temp, + &xhci->ir_set->erst_dequeue[0]); +} + +/* Ring the host controller doorbell after placing a command on the ring */ +void ring_cmd_db(struct xhci_hcd *xhci) +{ + u32 temp; + + xhci_dbg(xhci, "// Ding dong!\n"); + temp = xhci_readl(xhci, &xhci->dba->doorbell[0]) & DB_MASK; + xhci_writel(xhci, temp | DB_TARGET_HOST, &xhci->dba->doorbell[0]); + /* Flush PCI posted writes */ + xhci_readl(xhci, &xhci->dba->doorbell[0]); +} + +static void handle_cmd_completion(struct xhci_hcd *xhci, + struct xhci_event_cmd *event) +{ + u64 cmd_dma; + dma_addr_t cmd_dequeue_dma; + + /* Check completion code */ + if (GET_COMP_CODE(event->status) != COMP_SUCCESS) + xhci_dbg(xhci, "WARN: unsuccessful no-op command\n"); + + cmd_dma = (((u64) event->cmd_trb[1]) << 32) + event->cmd_trb[0]; + cmd_dequeue_dma = trb_virt_to_dma(xhci->cmd_ring->deq_seg, + xhci->cmd_ring->dequeue); + /* Is the command ring deq ptr out of sync with the deq seg ptr? */ + if (cmd_dequeue_dma == 0) { + xhci->error_bitmask |= 1 << 4; + return; + } + /* Does the DMA address match our internal dequeue pointer address? */ + if (cmd_dma != (u64) cmd_dequeue_dma) { + xhci->error_bitmask |= 1 << 5; + return; + } + switch (xhci->cmd_ring->dequeue->generic.field[3] & TRB_TYPE_BITMASK) { + case TRB_TYPE(TRB_CMD_NOOP): + ++xhci->noops_handled; + break; + default: + /* Skip over unknown commands on the event ring */ + xhci->error_bitmask |= 1 << 6; + break; + } + inc_deq(xhci, xhci->cmd_ring, false); +} + +void handle_event(struct xhci_hcd *xhci) +{ + union xhci_trb *event; + + if (!xhci->event_ring || !xhci->event_ring->dequeue) { + xhci->error_bitmask |= 1 << 1; + return; + } + + event = xhci->event_ring->dequeue; + /* Does the HC or OS own the TRB? */ + if ((event->event_cmd.flags & TRB_CYCLE) != + xhci->event_ring->cycle_state) { + xhci->error_bitmask |= 1 << 2; + return; + } + + /* FIXME: Only handles command completion events. */ + switch ((event->event_cmd.flags & TRB_TYPE_BITMASK)) { + case TRB_TYPE(TRB_COMPLETION): + handle_cmd_completion(xhci, &event->event_cmd); + break; + default: + xhci->error_bitmask |= 1 << 3; + } + + /* Update SW and HC event ring dequeue pointer */ + inc_deq(xhci, xhci->event_ring, true); + set_hc_event_deq(xhci); + /* Are there more items on the event ring? */ + handle_event(xhci); +} + +/* + * Generic function for queueing a TRB on a ring. + * The caller must have checked to make sure there's room on the ring. + */ +static void queue_trb(struct xhci_hcd *xhci, struct xhci_ring *ring, + bool consumer, + u32 field1, u32 field2, u32 field3, u32 field4) +{ + struct xhci_generic_trb *trb; + + trb = &ring->enqueue->generic; + trb->field[0] = field1; + trb->field[1] = field2; + trb->field[2] = field3; + trb->field[3] = field4; + inc_enq(xhci, ring, consumer); +} + +/* Generic function for queueing a command TRB on the command ring */ +static int queue_command(struct xhci_hcd *xhci, u32 field1, u32 field2, u32 field3, u32 field4) +{ + if (!room_on_ring(xhci, xhci->cmd_ring, 1)) { + if (!in_interrupt()) + xhci_err(xhci, "ERR: No room for command on command ring\n"); + return -ENOMEM; + } + queue_trb(xhci, xhci->cmd_ring, false, field1, field2, field3, + field4 | xhci->cmd_ring->cycle_state); + return 0; +} + +/* Queue a no-op command on the command ring */ +static int queue_cmd_noop(struct xhci_hcd *xhci) +{ + return queue_command(xhci, 0, 0, 0, TRB_TYPE(TRB_CMD_NOOP)); +} + +/* + * Place a no-op command on the command ring to test the command and + * event ring. + */ +void *setup_one_noop(struct xhci_hcd *xhci) +{ + if (queue_cmd_noop(xhci) < 0) + return NULL; + xhci->noops_submitted++; + return ring_cmd_db; +} diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index f168fcac5999..66be134b8921 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -24,6 +24,7 @@ #define __LINUX_XHCI_HCD_H #include <linux/usb.h> +#include <linux/timer.h> #include "../core/hcd.h" /* Code sharing between pci-quirks and xhci hcd */ @@ -377,6 +378,7 @@ struct intr_reg { /* irq_pending bitmasks */ #define ER_IRQ_PENDING(p) ((p) & 0x1) /* bits 2:31 need to be preserved */ +/* THIS IS BUGGY - FIXME - IP IS WRITE 1 TO CLEAR */ #define ER_IRQ_CLEAR(p) ((p) & 0xfffffffe) #define ER_IRQ_ENABLE(p) ((ER_IRQ_CLEAR(p)) | 0x2) #define ER_IRQ_DISABLE(p) ((ER_IRQ_CLEAR(p)) & ~(0x2)) @@ -699,11 +701,14 @@ struct xhci_link_trb { /* control bitfields */ #define LINK_TOGGLE (0x1<<1) +/* Command completion event TRB */ +struct xhci_event_cmd { + /* Pointer to command TRB, or the value passed by the event data trb */ + u32 cmd_trb[2]; + u32 status; + u32 flags; +} __attribute__ ((packed)); -union xhci_trb { - struct xhci_link_trb link; - struct xhci_transfer_event trans_event; -}; /* Normal TRB fields */ /* transfer_len bitmasks - bits 0:16 */ @@ -737,6 +742,17 @@ union xhci_trb { /* Control transfer TRB specific fields */ #define TRB_DIR_IN (1<<16) +struct xhci_generic_trb { + u32 field[4]; +} __attribute__ ((packed)); + +union xhci_trb { + struct xhci_link_trb link; + struct xhci_transfer_event trans_event; + struct xhci_event_cmd event_cmd; + struct xhci_generic_trb generic; +}; + /* TRB bit mask */ #define TRB_TYPE_BITMASK (0xfc00) #define TRB_TYPE(p) ((p) << 10) @@ -825,7 +841,11 @@ struct xhci_segment { struct xhci_ring { struct xhci_segment *first_seg; union xhci_trb *enqueue; + struct xhci_segment *enq_seg; + unsigned int enq_updates; union xhci_trb *dequeue; + struct xhci_segment *deq_seg; + unsigned int deq_updates; /* * Write the cycle state into the TRB cycle field to give ownership of * the TRB to the host controller (if we are the producer), or to check @@ -861,6 +881,8 @@ struct xhci_erst { #define ERST_SIZE 64 /* Initial number of event segment rings allocated */ #define ERST_ENTRIES 1 +/* Poll every 60 seconds */ +#define POLL_TIMEOUT 60 /* XXX: Make these module parameters */ @@ -907,8 +929,21 @@ struct xhci_hcd { /* DMA pools */ struct dma_pool *device_pool; struct dma_pool *segment_pool; + +#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING + /* Poll the rings - for debugging */ + struct timer_list event_ring_timer; + int zombie; +#endif + /* Statistics */ + int noops_submitted; + int noops_handled; + int error_bitmask; }; +/* For testing purposes */ +#define NUM_TEST_NOOPS 0 + /* convert between an HCD pointer and the corresponding EHCI_HCD */ static inline struct xhci_hcd *hcd_to_xhci(struct usb_hcd *hcd) { @@ -956,9 +991,11 @@ void xhci_print_ir_set(struct xhci_hcd *xhci, struct intr_reg *ir_set, int set_n void xhci_print_registers(struct xhci_hcd *xhci); void xhci_dbg_regs(struct xhci_hcd *xhci); void xhci_print_run_regs(struct xhci_hcd *xhci); +void xhci_debug_segment(struct xhci_hcd *xhci, struct xhci_segment *seg); void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring); void xhci_dbg_erst(struct xhci_hcd *xhci, struct xhci_erst *erst); void xhci_dbg_cmd_ptrs(struct xhci_hcd *xhci); +void xhci_dbg_ring_ptrs(struct xhci_hcd *xhci, struct xhci_ring *ring); /* xHCI memory managment */ void xhci_mem_cleanup(struct xhci_hcd *xhci); @@ -978,5 +1015,13 @@ int xhci_run(struct usb_hcd *hcd); void xhci_stop(struct usb_hcd *hcd); void xhci_shutdown(struct usb_hcd *hcd); int xhci_get_frame(struct usb_hcd *hcd); +irqreturn_t xhci_irq(struct usb_hcd *hcd); + +/* xHCI ring, segment, TRB, and TD functions */ +dma_addr_t trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); +void ring_cmd_db(struct xhci_hcd *xhci); +void *setup_one_noop(struct xhci_hcd *xhci); +void handle_event(struct xhci_hcd *xhci); +void set_hc_event_deq(struct xhci_hcd *xhci); #endif /* __LINUX_XHCI_HCD_H */ -- cgit v1.2.3 From 6b403b020c1f42180b14d28d832da61167cff822 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:54:10 -0700 Subject: USB: Add SuperSpeed to the list of USB device speeds. Modify the USB core to handle the new USB 3.0 speed, "SuperSpeed". This is 5.0 Gbps (wire speed). There are probably more places that check for speed that I've missed. SuperSpeed devices have a 512 byte endpoint 0 max packet size. This shows up as a bMaxPacketSize0 set to 0x09 (see table 9-8 of the USB 3.0 bus spec). xHCI spec says that the xHC can handle intervals up to 2^15 microframes. That might change when real silicon becomes available. Add FIXME note for SuperSpeed isochronous endpoints. They can transmit up to 16 packets in one "burst" before they wait for an acknowledgment of the packets. They can do up to 3 bursts per microframe (determined by the mult value in the endpoint companion descriptor). The xHCI driver doesn't have support for isoc yet, so fix this later. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/config.c | 1 + drivers/usb/core/hcd.c | 16 ++++++++++++++-- drivers/usb/core/hcd.h | 1 + drivers/usb/core/hub.c | 11 +++++++++-- drivers/usb/core/urb.c | 6 ++++++ include/linux/usb/ch9.h | 1 + 6 files changed, 32 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 568244c99bdc..e9426acf5682 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -92,6 +92,7 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum, if (usb_endpoint_xfer_int(d)) { i = 1; switch (to_usb_device(ddev)->speed) { + case USB_SPEED_SUPER: case USB_SPEED_HIGH: /* Many device manufacturers are using full-speed * bInterval values in high-speed interrupt endpoint diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index c65d95604028..120bb56c4b84 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1870,8 +1870,20 @@ int usb_add_hcd(struct usb_hcd *hcd, retval = -ENOMEM; goto err_allocate_root_hub; } - rhdev->speed = (hcd->driver->flags & HCD_USB2) ? USB_SPEED_HIGH : - USB_SPEED_FULL; + + switch (hcd->driver->flags & HCD_MASK) { + case HCD_USB11: + rhdev->speed = USB_SPEED_FULL; + break; + case HCD_USB2: + rhdev->speed = USB_SPEED_HIGH; + break; + case HCD_USB3: + rhdev->speed = USB_SPEED_SUPER; + break; + default: + goto err_allocate_root_hub; + } hcd->self.root_hub = rhdev; /* wakeup flag init defaults to "everything works" for root hubs, diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 7a47498b0aac..4f6ee60d97c6 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -174,6 +174,7 @@ struct hc_driver { #define HCD_USB11 0x0010 /* USB 1.1 */ #define HCD_USB2 0x0020 /* USB 2.0 */ #define HCD_USB3 0x0040 /* USB 3.0 */ +#define HCD_MASK 0x0070 /* called to init HCD and root hub */ int (*reset) (struct usb_hcd *hcd); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index c7a1a1d4bcb1..fa0208e0da6a 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2471,6 +2471,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, * reported as 0xff in the device descriptor). WUSB1.0[4.8.1]. */ switch (udev->speed) { + case USB_SPEED_SUPER: case USB_SPEED_VARIABLE: /* fixed at 512 */ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512); break; @@ -2496,6 +2497,9 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, case USB_SPEED_LOW: speed = "low"; break; case USB_SPEED_FULL: speed = "full"; break; case USB_SPEED_HIGH: speed = "high"; break; + case USB_SPEED_SUPER: + speed = "super"; + break; case USB_SPEED_VARIABLE: speed = "variable"; type = "Wireless "; @@ -2634,8 +2638,11 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, if (retval) goto fail; - i = udev->descriptor.bMaxPacketSize0 == 0xff? /* wusb device? */ - 512 : udev->descriptor.bMaxPacketSize0; + if (udev->descriptor.bMaxPacketSize0 == 0xff || + udev->speed == USB_SPEED_SUPER) + i = 512; + else + i = udev->descriptor.bMaxPacketSize0; if (le16_to_cpu(udev->ep0.desc.wMaxPacketSize) != i) { if (udev->speed != USB_SPEED_FULL || !(i == 8 || i == 16 || i == 32 || i == 64)) { diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 3376055f36e7..02eb0ef7a4c3 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -351,6 +351,7 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) if (xfertype == USB_ENDPOINT_XFER_ISOC) { int n, len; + /* FIXME SuperSpeed isoc endpoints have up to 16 bursts */ /* "high bandwidth" mode, 1-3 packets/uframe? */ if (dev->speed == USB_SPEED_HIGH) { int mult = 1 + ((max >> 11) & 0x03); @@ -426,6 +427,11 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) return -EINVAL; /* too big? */ switch (dev->speed) { + case USB_SPEED_SUPER: /* units are 125us */ + /* Handle up to 2^(16-1) microframes */ + if (urb->interval > (1 << 15)) + return -EINVAL; + max = 1 << 15; case USB_SPEED_HIGH: /* units are microframes */ /* NOTE usb handles 2^15 */ if (urb->interval > (1024 * 8)) diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h index b145119a90da..93bfe6352342 100644 --- a/include/linux/usb/ch9.h +++ b/include/linux/usb/ch9.h @@ -752,6 +752,7 @@ enum usb_device_speed { USB_SPEED_LOW, USB_SPEED_FULL, /* usb 1.1 */ USB_SPEED_HIGH, /* usb 2.0 */ USB_SPEED_VARIABLE, /* wireless (usb 2.5) */ + USB_SPEED_SUPER, /* usb 3.0 */ }; enum usb_device_state { -- cgit v1.2.3 From d2e9b4d6734db2327af3149d8ca7555307e10828 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:55:01 -0700 Subject: USB: Add USB 3.0 roothub support to USB core. Add USB 3.0 root hub descriptors. This is a kludge because I reused the old USB 2.0 descriptors, instead of using the new USB 3.0 hub descriptors with endpoint companion descriptors and other descriptors. I did this because I wasn't ready to add USB 3.0 hub changes to khubd. For now, a USB 3.0 roothub looks like a USB 2.0 roothub, with a higher speed. USB 3.0 hubs have no transaction translator (TT). Make USB core debugging handle super speed ports. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hcd.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++-- drivers/usb/core/hub.c | 5 ++++ drivers/usb/core/hub.h | 3 +++ 3 files changed, 77 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 120bb56c4b84..823744d80cb9 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -128,6 +128,27 @@ static inline int is_root_hub(struct usb_device *udev) #define KERNEL_REL ((LINUX_VERSION_CODE >> 16) & 0x0ff) #define KERNEL_VER ((LINUX_VERSION_CODE >> 8) & 0x0ff) +/* usb 3.0 root hub device descriptor */ +static const u8 usb3_rh_dev_descriptor[18] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x00, 0x03, /* __le16 bcdUSB; v3.0 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x03, /* __u8 bDeviceProtocol; USB 3.0 hub */ + 0x09, /* __u8 bMaxPacketSize0; 2^9 = 512 Bytes */ + + 0x6b, 0x1d, /* __le16 idVendor; Linux Foundation */ + 0x02, 0x00, /* __le16 idProduct; device 0x0002 */ + KERNEL_VER, KERNEL_REL, /* __le16 bcdDevice */ + + 0x03, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + /* usb 2.0 root hub device descriptor */ static const u8 usb2_rh_dev_descriptor [18] = { 0x12, /* __u8 bLength; */ @@ -273,6 +294,47 @@ static const u8 hs_rh_config_descriptor [] = { 0x0c /* __u8 ep_bInterval; (256ms -- usb 2.0 spec) */ }; +static const u8 ss_rh_config_descriptor[] = { + /* one configuration */ + 0x09, /* __u8 bLength; */ + 0x02, /* __u8 bDescriptorType; Configuration */ + 0x19, 0x00, /* __le16 wTotalLength; FIXME */ + 0x01, /* __u8 bNumInterfaces; (1) */ + 0x01, /* __u8 bConfigurationValue; */ + 0x00, /* __u8 iConfiguration; */ + 0xc0, /* __u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 0x00, /* __u8 MaxPower; */ + + /* one interface */ + 0x09, /* __u8 if_bLength; */ + 0x04, /* __u8 if_bDescriptorType; Interface */ + 0x00, /* __u8 if_bInterfaceNumber; */ + 0x00, /* __u8 if_bAlternateSetting; */ + 0x01, /* __u8 if_bNumEndpoints; */ + 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ + 0x00, /* __u8 if_bInterfaceSubClass; */ + 0x00, /* __u8 if_bInterfaceProtocol; */ + 0x00, /* __u8 if_iInterface; */ + + /* one endpoint (status change endpoint) */ + 0x07, /* __u8 ep_bLength; */ + 0x05, /* __u8 ep_bDescriptorType; Endpoint */ + 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* __u8 ep_bmAttributes; Interrupt */ + /* __le16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) + * see hub.c:hub_configure() for details. */ + (USB_MAXCHILDREN + 1 + 7) / 8, 0x00, + 0x0c /* __u8 ep_bInterval; (256ms -- usb 2.0 spec) */ + /* + * All 3.0 hubs should have an endpoint companion descriptor, + * but we're ignoring that for now. FIXME? + */ +}; + /*-------------------------------------------------------------------------*/ /* @@ -426,7 +488,9 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) case DeviceRequest | USB_REQ_GET_DESCRIPTOR: switch (wValue & 0xff00) { case USB_DT_DEVICE << 8: - if (hcd->driver->flags & HCD_USB2) + if (hcd->driver->flags & HCD_USB3) + bufp = usb3_rh_dev_descriptor; + else if (hcd->driver->flags & HCD_USB2) bufp = usb2_rh_dev_descriptor; else if (hcd->driver->flags & HCD_USB11) bufp = usb11_rh_dev_descriptor; @@ -437,7 +501,10 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) patch_protocol = 1; break; case USB_DT_CONFIG << 8: - if (hcd->driver->flags & HCD_USB2) { + if (hcd->driver->flags & HCD_USB3) { + bufp = ss_rh_config_descriptor; + len = sizeof ss_rh_config_descriptor; + } else if (hcd->driver->flags & HCD_USB2) { bufp = hs_rh_config_descriptor; len = sizeof hs_rh_config_descriptor; } else { diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index fa0208e0da6a..f3fe8dfaaad1 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -155,6 +155,8 @@ static inline char *portspeed(int portstatus) return "480 Mb/s"; else if (portstatus & (1 << USB_PORT_FEAT_LOWSPEED)) return "1.5 Mb/s"; + else if (portstatus & (1 << USB_PORT_FEAT_SUPERSPEED)) + return "5.0 Gb/s"; else return "12 Mb/s"; } @@ -951,6 +953,9 @@ static int hub_configure(struct usb_hub *hub, ret); hub->tt.hub = hdev; break; + case 3: + /* USB 3.0 hubs don't have a TT */ + break; default: dev_dbg(hub_dev, "Unrecognized hub protocol %d\n", hdev->descriptor.bDeviceProtocol); diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 2a116ce53c9b..889c0f32a40b 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -47,7 +47,10 @@ #define USB_PORT_FEAT_L1 5 /* L1 suspend */ #define USB_PORT_FEAT_POWER 8 #define USB_PORT_FEAT_LOWSPEED 9 +/* This value was never in Table 11-17 */ #define USB_PORT_FEAT_HIGHSPEED 10 +/* This value is also fake */ +#define USB_PORT_FEAT_SUPERSPEED 11 #define USB_PORT_FEAT_C_CONNECTION 16 #define USB_PORT_FEAT_C_ENABLE 17 #define USB_PORT_FEAT_C_SUSPEND 18 -- cgit v1.2.3 From e7b7717247f61e2cf18ec47f91999065c59d1607 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:54:26 -0700 Subject: USB: Don't reset USB 3.0 devices on port change detection. The USB 3.0 bus specification defines a new connection sequence for USB 3.0 hubs and roothubs. USB 3.0 devices are reset and link trained by the hub before the port status change notification is sent to the host OS. This means that an entire tree of devices can be trained in parallel on power up, and the OS no longer needs to reset USB 3.0 devices. Change the USB core's hub port init sequence so that it does not reset USB 3.0 devices. The port status change from the roothub and from the USB 3.0 hub will report the SuperSpeed connect correctly. This patch currently only handles the roothub case. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hub.c | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index f3fe8dfaaad1..3c28bde6cbd5 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2435,6 +2435,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, static DEFINE_MUTEX(usb_address0_mutex); struct usb_device *hdev = hub->hdev; + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); int i, j, retval; unsigned delay = HUB_SHORT_RESET_TIME; enum usb_device_speed oldspeed = udev->speed; @@ -2457,11 +2458,24 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, mutex_lock(&usb_address0_mutex); - /* Reset the device; full speed may morph to high speed */ - retval = hub_port_reset(hub, port1, udev, delay); - if (retval < 0) /* error or disconnect */ + if ((hcd->driver->flags & HCD_USB3) && udev->config) { + /* FIXME this will need special handling by the xHCI driver. */ + dev_dbg(&udev->dev, + "xHCI reset of configured device " + "not supported yet.\n"); + retval = -EINVAL; goto fail; - /* success, speed is known */ + } else if (!udev->config && oldspeed == USB_SPEED_SUPER) { + /* Don't reset USB 3.0 devices during an initial setup */ + usb_set_device_state(udev, USB_STATE_DEFAULT); + } else { + /* Reset the device; full speed may morph to high speed */ + /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */ + retval = hub_port_reset(hub, port1, udev, delay); + if (retval < 0) /* error or disconnect */ + goto fail; + /* success, speed is known */ + } retval = -ENODEV; if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed) { @@ -2859,7 +2873,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, } usb_set_device_state(udev, USB_STATE_POWERED); - udev->speed = USB_SPEED_UNKNOWN; udev->bus_mA = hub->mA_per_port; udev->level = hdev->level + 1; udev->wusb = hub_is_wusb(hub); @@ -2871,7 +2884,24 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, goto loop; } - /* reset and get descriptor */ + /* + * USB 3.0 devices are reset automatically before the connect + * port status change appears, and the root hub port status + * shows the correct speed. We also get port change + * notifications for USB 3.0 devices from the USB 3.0 portion of + * an external USB 3.0 hub, but this isn't handled correctly yet + * FIXME. + */ + + if (!(hcd->driver->flags & HCD_USB3)) + udev->speed = USB_SPEED_UNKNOWN; + else if ((hdev->parent == NULL) && + (portstatus & (1 << USB_PORT_FEAT_SUPERSPEED))) + udev->speed = USB_SPEED_SUPER; + else + udev->speed = USB_SPEED_UNKNOWN; + + /* reset (non-USB 3.0 devices) and get descriptor */ status = hub_port_init(hub, udev, port1, i); if (status < 0) goto loop; -- cgit v1.2.3 From 7206b00164a1c3ca533e01db285955617e1019f8 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:54:49 -0700 Subject: USB: Add route string to struct usb_device. This patch adds a hex route string to each USB device. The route string is used by the USB 3.0 host controller to send packets through the device tree. USB 3.0 hubs use this string to route packets to the correct port. This is fundamental bus change from USB 2.0, where all packets were broadcast across the bus. Devices (including hubs) under a root port receive the route string 0x0. Every four bits in the route string represent a port on a hub. This length works because USB 3.0 hubs are limited to 15 ports, and USB 2.0 hubs (with potentially more ports) will never see packets with a route string. A port number of 0 means the packet is destined for that hub. For example, a peripheral device might have a route string of 0x00097. This means the device is connected to port 9 of the hub at depth 1. The hub at depth 1 is connected to port 7 of a hub at depth 0. The hub at depth 0 is connected to a root port. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/usb.c | 10 ++++++++-- include/linux/usb.h | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 020b58528d90..f026991d0bdf 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -375,18 +375,24 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, */ if (unlikely(!parent)) { dev->devpath[0] = '0'; + dev->route = 0; dev->dev.parent = bus->controller; dev_set_name(&dev->dev, "usb%d", bus->busnum); root_hub = 1; } else { /* match any labeling on the hubs; it's one-based */ - if (parent->devpath[0] == '0') + if (parent->devpath[0] == '0') { snprintf(dev->devpath, sizeof dev->devpath, "%d", port1); - else + /* Root ports are not counted in route string */ + dev->route = 0; + } else { snprintf(dev->devpath, sizeof dev->devpath, "%s.%d", parent->devpath, port1); + dev->route = parent->route + + (port1 << ((parent->level - 1)*4)); + } dev->dev.parent = &parent->dev; dev_set_name(&dev->dev, "%d-%s", bus->busnum, dev->devpath); diff --git a/include/linux/usb.h b/include/linux/usb.h index fb1f2a32ae05..2b380a16c62f 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -362,6 +362,7 @@ struct usb_tt; * struct usb_device - kernel's representation of a USB device * @devnum: device number; address on a USB bus * @devpath: device ID string for use in messages (e.g., /port/...) + * @route: tree topology hex string for use with xHCI * @state: device state: configured, not attached, etc. * @speed: device speed: high/full/low (or error) * @tt: Transaction Translator info; used with low/full speed dev, highspeed hub @@ -427,6 +428,7 @@ struct usb_tt; struct usb_device { int devnum; char devpath [16]; + u32 route; enum usb_device_state state; enum usb_device_speed speed; -- cgit v1.2.3 From 0f2a79300a1471cf92ab43af165ea13555c8b0a5 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:57:12 -0700 Subject: USB: xhci: Root hub support. Add functionality for getting port status and hub descriptor for xHCI root hubs. This is WIP because the USB 3.0 hub descriptor is different from the USB 2.0 hub descriptor. For now, we lie about the root hub descriptor because the changes won't effect how the core talks to the root hub. Later we will need to add the USB 3.0 hub descriptor for real hubs, and this code might change. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-dbg.c | 26 ++++ drivers/usb/host/xhci-hcd.c | 3 + drivers/usb/host/xhci-hub.c | 308 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-pci.c | 4 +- drivers/usb/host/xhci-ring.c | 43 +++++- drivers/usb/host/xhci.h | 11 ++ 6 files changed, 390 insertions(+), 5 deletions(-) create mode 100644 drivers/usb/host/xhci-hub.c (limited to 'drivers') diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 6dbf7d856f80..570cd4820458 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -152,6 +152,31 @@ void xhci_print_op_regs(struct xhci_hcd *xhci) xhci_print_status(xhci); } +void xhci_print_ports(struct xhci_hcd *xhci) +{ + u32 __iomem *addr; + int i, j; + int ports; + char *names[NUM_PORT_REGS] = { + "status", + "power", + "link", + "reserved", + }; + + ports = HCS_MAX_PORTS(xhci->hcs_params1); + addr = &xhci->op_regs->port_status_base; + for (i = 0; i < ports; i++) { + for (j = 0; j < NUM_PORT_REGS; ++j) { + xhci_dbg(xhci, "0x%x port %s reg = 0x%x\n", + (unsigned int) addr, + names[j], + (unsigned int) xhci_readl(xhci, addr)); + addr++; + } + } +} + void xhci_print_ir_set(struct xhci_hcd *xhci, struct intr_reg *ir_set, int set_num) { void *addr; @@ -228,6 +253,7 @@ void xhci_print_registers(struct xhci_hcd *xhci) { xhci_print_cap_regs(xhci); xhci_print_op_regs(xhci); + xhci_print_ports(xhci); } void xhci_print_trb_offsets(struct xhci_hcd *xhci, union xhci_trb *trb) diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index a99c119e9fd9..d7c2fed55978 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -349,6 +349,9 @@ int xhci_run(struct usb_hcd *hcd) struct xhci_hcd *xhci = hcd_to_xhci(hcd); void (*doorbell)(struct xhci_hcd *) = NULL; + hcd->uses_new_polling = 1; + hcd->poll_rh = 0; + xhci_dbg(xhci, "xhci_run\n"); #if 0 /* FIXME: MSI not setup yet */ /* Do this at the very last minute */ diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c new file mode 100644 index 000000000000..eac5b53aa9e7 --- /dev/null +++ b/drivers/usb/host/xhci-hub.c @@ -0,0 +1,308 @@ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <asm/unaligned.h> + +#include "xhci.h" + +static void xhci_hub_descriptor(struct xhci_hcd *xhci, + struct usb_hub_descriptor *desc) +{ + int ports; + u16 temp; + + ports = HCS_MAX_PORTS(xhci->hcs_params1); + + /* USB 3.0 hubs have a different descriptor, but we fake this for now */ + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; /* xhci section 5.4.9 says 20ms max */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* Why does core/hcd.h define bitmap? It's just confusing. */ + memset(&desc->DeviceRemovable[0], 0, temp); + memset(&desc->DeviceRemovable[temp], 0xff, temp); + + /* Ugh, these should be #defines, FIXME */ + /* Using table 11-13 in USB 2.0 spec. */ + temp = 0; + /* Bits 1:0 - support port power switching, or power always on */ + if (HCC_PPC(xhci->hcc_params)) + temp |= 0x0001; + else + temp |= 0x0002; + /* Bit 2 - root hubs are not part of a compound device */ + /* Bits 4:3 - individual port over current protection */ + temp |= 0x0008; + /* Bits 6:5 - no TTs in root ports */ + /* Bit 7 - no port indicators */ + desc->wHubCharacteristics = (__force __u16) cpu_to_le16(temp); +} + +static unsigned int xhci_port_speed(unsigned int port_status) +{ + if (DEV_LOWSPEED(port_status)) + return 1 << USB_PORT_FEAT_LOWSPEED; + if (DEV_HIGHSPEED(port_status)) + return 1 << USB_PORT_FEAT_HIGHSPEED; + if (DEV_SUPERSPEED(port_status)) + return 1 << USB_PORT_FEAT_SUPERSPEED; + /* + * FIXME: Yes, we should check for full speed, but the core uses that as + * a default in portspeed() in usb/core/hub.c (which is the only place + * USB_PORT_FEAT_*SPEED is used). + */ + return 0; +} + +/* + * These bits are Read Only (RO) and should be saved and written to the + * registers: 0, 3, 10:13, 30 + * connect status, over-current status, port speed, and device removable. + * connect status and port speed are also sticky - meaning they're in + * the AUX well and they aren't changed by a hot, warm, or cold reset. + */ +#define XHCI_PORT_RO ((1<<0) | (1<<3) | (0xf<<10) | (1<<30)) +/* + * These bits are RW; writing a 0 clears the bit, writing a 1 sets the bit: + * bits 5:8, 9, 14:15, 25:27 + * link state, port power, port indicator state, "wake on" enable state + */ +#define XHCI_PORT_RWS ((0xf<<5) | (1<<9) | (0x3<<14) | (0x7<<25)) +/* + * These bits are RW; writing a 1 sets the bit, writing a 0 has no effect: + * bit 4 (port reset) + */ +#define XHCI_PORT_RW1S ((1<<4)) +/* + * These bits are RW; writing a 1 clears the bit, writing a 0 has no effect: + * bits 1, 17, 18, 19, 20, 21, 22, 23 + * port enable/disable, and + * change bits: connect, PED, warm port reset changed (reserved zero for USB 2.0 ports), + * over-current, reset, link state, and L1 change + */ +#define XHCI_PORT_RW1CS ((1<<1) | (0x7f<<17)) +/* + * Bit 16 is RW, and writing a '1' to it causes the link state control to be + * latched in + */ +#define XHCI_PORT_RW ((1<<16)) +/* + * These bits are Reserved Zero (RsvdZ) and zero should be written to them: + * bits 2, 24, 28:31 + */ +#define XHCI_PORT_RZ ((1<<2) | (1<<24) | (0xf<<28)) + +/* + * Given a port state, this function returns a value that would result in the + * port being in the same state, if the value was written to the port status + * control register. + * Save Read Only (RO) bits and save read/write bits where + * writing a 0 clears the bit and writing a 1 sets the bit (RWS). + * For all other types (RW1S, RW1CS, RW, and RZ), writing a '0' has no effect. + */ +static u32 xhci_port_state_to_neutral(u32 state) +{ + /* Save read-only status and port state */ + return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); +} + +int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ports; + unsigned long flags; + u32 temp, status; + int retval = 0; + u32 __iomem *addr; + char *port_change_bit; + + ports = HCS_MAX_PORTS(xhci->hcs_params1); + + spin_lock_irqsave(&xhci->lock, flags); + switch (typeReq) { + case GetHubStatus: + /* No power source, over-current reported per port */ + memset(buf, 0, 4); + break; + case GetHubDescriptor: + xhci_hub_descriptor(xhci, (struct usb_hub_descriptor *) buf); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + addr = &xhci->op_regs->port_status_base + NUM_PORT_REGS*(wIndex & 0xff); + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, "get port status, actual port %d status = 0x%x\n", wIndex, temp); + + /* wPortChange bits */ + if (temp & PORT_CSC) + status |= 1 << USB_PORT_FEAT_C_CONNECTION; + if (temp & PORT_PEC) + status |= 1 << USB_PORT_FEAT_C_ENABLE; + if ((temp & PORT_OCC)) + status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; + /* + * FIXME ignoring suspend, reset, and USB 2.1/3.0 specific + * changes + */ + if (temp & PORT_CONNECT) { + status |= 1 << USB_PORT_FEAT_CONNECTION; + status |= xhci_port_speed(temp); + } + if (temp & PORT_PE) + status |= 1 << USB_PORT_FEAT_ENABLE; + if (temp & PORT_OC) + status |= 1 << USB_PORT_FEAT_OVER_CURRENT; + if (temp & PORT_RESET) + status |= 1 << USB_PORT_FEAT_RESET; + if (temp & PORT_POWER) + status |= 1 << USB_PORT_FEAT_POWER; + xhci_dbg(xhci, "Get port status returned 0x%x\n", status); + put_unaligned(cpu_to_le32(status), (__le32 *) buf); + break; + case SetPortFeature: + wIndex &= 0xff; + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + addr = &xhci->op_regs->port_status_base + NUM_PORT_REGS*(wIndex & 0xff); + temp = xhci_readl(xhci, addr); + temp = xhci_port_state_to_neutral(temp); + switch (wValue) { + case USB_PORT_FEAT_POWER: + /* + * Turn on ports, even if there isn't per-port switching. + * HC will report connect events even before this is set. + * However, khubd will ignore the roothub events until + * the roothub is registered. + */ + xhci_writel(xhci, temp | PORT_POWER, addr); + + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, "set port power, actual port %d status = 0x%x\n", wIndex, temp); + break; + case USB_PORT_FEAT_RESET: + temp = (temp | PORT_RESET); + xhci_writel(xhci, temp, addr); + + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, "set port reset, actual port %d status = 0x%x\n", wIndex, temp); + break; + default: + goto error; + } + temp = xhci_readl(xhci, addr); /* unblock any posted writes */ + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + addr = &xhci->op_regs->port_status_base + + NUM_PORT_REGS*(wIndex & 0xff); + temp = xhci_readl(xhci, addr); + temp = xhci_port_state_to_neutral(temp); + switch (wValue) { + case USB_PORT_FEAT_C_RESET: + status = PORT_RC; + port_change_bit = "reset"; + break; + case USB_PORT_FEAT_C_CONNECTION: + status = PORT_CSC; + port_change_bit = "connect"; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + status = PORT_OCC; + port_change_bit = "over-current"; + break; + default: + goto error; + } + /* Change bits are all write 1 to clear */ + xhci_writel(xhci, temp | status, addr); + temp = xhci_readl(xhci, addr); + xhci_dbg(xhci, "clear port %s change, actual port %d status = 0x%x\n", + port_change_bit, wIndex, temp); + temp = xhci_readl(xhci, addr); /* unblock any posted writes */ + break; + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&xhci->lock, flags); + return retval; +} + +/* + * Returns 0 if the status hasn't changed, or the number of bytes in buf. + * Ports are 0-indexed from the HCD point of view, + * and 1-indexed from the USB core pointer of view. + * xHCI instances can have up to 127 ports, so FIXME if you see more than 15. + * + * Note that the status change bits will be cleared as soon as a port status + * change event is generated, so we use the saved status from that event. + */ +int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + unsigned long flags; + u32 temp, status; + int i, retval; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ports; + u32 __iomem *addr; + + ports = HCS_MAX_PORTS(xhci->hcs_params1); + + /* Initial status is no changes */ + buf[0] = 0; + status = 0; + if (ports > 7) { + buf[1] = 0; + retval = 2; + } else { + retval = 1; + } + + spin_lock_irqsave(&xhci->lock, flags); + /* For each port, did anything change? If so, set that bit in buf. */ + for (i = 0; i < ports; i++) { + addr = &xhci->op_regs->port_status_base + + NUM_PORT_REGS*i; + temp = xhci_readl(xhci, addr); + if (temp & (PORT_CSC | PORT_PEC | PORT_OCC)) { + if (i < 7) + buf[0] |= 1 << (i + 1); + else + buf[1] |= 1 << (i - 7); + status = 1; + } + } + spin_unlock_irqrestore(&xhci->lock, flags); + return status ? retval : 0; +} diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 89614af80d20..005c5b264a7c 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -113,7 +113,9 @@ static const struct hc_driver xhci_pci_hc_driver = { */ .get_frame_number = xhci_get_frame, - /* Implement root hub support later. */ + /* Root hub support */ + .hub_control = xhci_hub_control, + .hub_status_data = xhci_hub_status_data, }; /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index c7e3c7142b9d..9d6bb3d730c4 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -284,9 +284,38 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, inc_deq(xhci, xhci->cmd_ring, false); } +static void handle_port_status(struct xhci_hcd *xhci, + union xhci_trb *event) +{ + u32 port_id; + + /* Port status change events always have a successful completion code */ + if (GET_COMP_CODE(event->generic.field[2]) != COMP_SUCCESS) { + xhci_warn(xhci, "WARN: xHC returned failed port status event\n"); + xhci->error_bitmask |= 1 << 8; + } + /* FIXME: core doesn't care about all port link state changes yet */ + port_id = GET_PORT_ID(event->generic.field[0]); + xhci_dbg(xhci, "Port Status Change Event for port %d\n", port_id); + + /* Update event ring dequeue pointer before dropping the lock */ + inc_deq(xhci, xhci->event_ring, true); + set_hc_event_deq(xhci); + + spin_unlock(&xhci->lock); + /* Pass this up to the core */ + usb_hcd_poll_rh_status(xhci_to_hcd(xhci)); + spin_lock(&xhci->lock); +} + +/* + * This function handles all OS-owned events on the event ring. It may drop + * xhci->lock between event processing (e.g. to pass up port status changes). + */ void handle_event(struct xhci_hcd *xhci) { union xhci_trb *event; + int update_ptrs = 1; if (!xhci->event_ring || !xhci->event_ring->dequeue) { xhci->error_bitmask |= 1 << 1; @@ -301,18 +330,24 @@ void handle_event(struct xhci_hcd *xhci) return; } - /* FIXME: Only handles command completion events. */ + /* FIXME: Handle more event types. */ switch ((event->event_cmd.flags & TRB_TYPE_BITMASK)) { case TRB_TYPE(TRB_COMPLETION): handle_cmd_completion(xhci, &event->event_cmd); break; + case TRB_TYPE(TRB_PORT_STATUS): + handle_port_status(xhci, event); + update_ptrs = 0; + break; default: xhci->error_bitmask |= 1 << 3; } - /* Update SW and HC event ring dequeue pointer */ - inc_deq(xhci, xhci->event_ring, true); - set_hc_event_deq(xhci); + if (update_ptrs) { + /* Update SW and HC event ring dequeue pointer */ + inc_deq(xhci, xhci->event_ring, true); + set_hc_event_deq(xhci); + } /* Are there more items on the event ring? */ handle_event(xhci); } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 66be134b8921..059c659d3f39 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -35,6 +35,8 @@ /* Max number of USB devices for any host controller - limit in section 6.1 */ #define MAX_HC_SLOTS 256 +/* Section 5.3.3 - MaxPorts */ +#define MAX_HC_PORTS 127 /* * xHCI register interface. @@ -710,6 +712,10 @@ struct xhci_event_cmd { } __attribute__ ((packed)); +/* Port Status Change Event TRB fields */ +/* Port ID - bits 31:24 */ +#define GET_PORT_ID(p) (((p) & (0xff << 24)) >> 24) + /* Normal TRB fields */ /* transfer_len bitmasks - bits 0:16 */ #define TRB_LEN(p) ((p) & 0x1ffff) @@ -1024,4 +1030,9 @@ void *setup_one_noop(struct xhci_hcd *xhci); void handle_event(struct xhci_hcd *xhci); void set_hc_event_deq(struct xhci_hcd *xhci); +/* xHCI roothub code */ +int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); +int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); + #endif /* __LINUX_XHCI_HCD_H */ -- cgit v1.2.3 From c6515272b858742962c1de0f3bf497a048b9abd7 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:57:26 -0700 Subject: USB: Support for addressing a USB device under xHCI Add host controller driver API and a slot_id variable to struct usb_device. This allows the xHCI host controller driver to ask the hardware to allocate a slot for the device when a struct usb_device is allocated. The slot needs to be allocated at that point because the hardware can run out of internal resources, and we want to know that very early in the device connection process. Don't call this new API for root hubs, since they aren't real devices. Add HCD API to let the host controller choose the device address. This is especially important for xHCI hardware running in a virtualized environment. The guests running under the VM don't need to know which addresses on the bus are taken, because the hardware picks the address for them. Announce SuperSpeed USB devices after the address has been assigned by the hardware. Don't use the new get descriptor/set address scheme with xHCI. Unless special handling is done in the host controller driver, the xHC can't issue control transfers before you set the device address. Support for the older addressing scheme will be added when the xHCI driver supports the Block Set Address Request (BSR) flag in the Address Device command. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hcd.h | 8 ++++++ drivers/usb/core/hub.c | 74 +++++++++++++++++++++++++++++++++++++------------- drivers/usb/core/usb.c | 14 +++++++++- include/linux/usb.h | 2 ++ 4 files changed, 78 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 4f6ee60d97c6..ae6d9db41ca9 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -226,6 +226,14 @@ struct hc_driver { void (*relinquish_port)(struct usb_hcd *, int); /* has a port been handed over to a companion? */ int (*port_handed_over)(struct usb_hcd *, int); + + /* xHCI specific functions */ + /* Called by usb_alloc_dev to alloc HC device structures */ + int (*alloc_dev)(struct usb_hcd *, struct usb_device *); + /* Called by usb_release_dev to free HC device structures */ + void (*free_dev)(struct usb_hcd *, struct usb_device *); + /* Returns the hardware-chosen device address */ + int (*address_device)(struct usb_hcd *, struct usb_device *udev); }; extern int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 3c28bde6cbd5..2af3b4f06054 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1328,6 +1328,11 @@ EXPORT_SYMBOL_GPL(usb_set_device_state); * 0 is reserved by USB for default address; (b) Linux's USB stack * uses always #1 for the root hub of the controller. So USB stack's * port #1, which is wusb virtual-port #0 has address #2. + * + * Devices connected under xHCI are not as simple. The host controller + * supports virtualization, so the hardware assigns device addresses and + * the HCD must setup data structures before issuing a set address + * command to the hardware. */ static void choose_address(struct usb_device *udev) { @@ -1647,6 +1652,9 @@ int usb_new_device(struct usb_device *udev) err = usb_configure_device(udev); /* detect & probe dev/intfs */ if (err < 0) goto fail; + dev_dbg(&udev->dev, "udev %d, busnum %d, minor = %d\n", + udev->devnum, udev->bus->busnum, + (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); /* export the usbdev device-node for libusb */ udev->dev.devt = MKDEV(USB_DEVICE_MAJOR, (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); @@ -2400,19 +2408,29 @@ EXPORT_SYMBOL_GPL(usb_ep0_reinit); static int hub_set_address(struct usb_device *udev, int devnum) { int retval; + struct usb_hcd *hcd = bus_to_hcd(udev->bus); - if (devnum <= 1) + /* + * The host controller will choose the device address, + * instead of the core having chosen it earlier + */ + if (!hcd->driver->address_device && devnum <= 1) return -EINVAL; if (udev->state == USB_STATE_ADDRESS) return 0; if (udev->state != USB_STATE_DEFAULT) return -EINVAL; - retval = usb_control_msg(udev, usb_sndaddr0pipe(), - USB_REQ_SET_ADDRESS, 0, devnum, 0, - NULL, 0, USB_CTRL_SET_TIMEOUT); + if (hcd->driver->address_device) { + retval = hcd->driver->address_device(hcd, udev); + } else { + retval = usb_control_msg(udev, usb_sndaddr0pipe(), + USB_REQ_SET_ADDRESS, 0, devnum, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); + if (retval == 0) + update_address(udev, devnum); + } if (retval == 0) { /* Device now using proper address. */ - update_address(udev, devnum); usb_set_device_state(udev, USB_STATE_ADDRESS); usb_ep0_reinit(udev); } @@ -2525,10 +2543,11 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, break; default: speed = "?"; break; } - dev_info (&udev->dev, - "%s %s speed %sUSB device using %s and address %d\n", - (udev->config) ? "reset" : "new", speed, type, - udev->bus->controller->driver->name, devnum); + if (udev->speed != USB_SPEED_SUPER) + dev_info(&udev->dev, + "%s %s speed %sUSB device using %s and address %d\n", + (udev->config) ? "reset" : "new", speed, type, + udev->bus->controller->driver->name, devnum); /* Set up TT records, if needed */ if (hdev->tt) { @@ -2553,7 +2572,11 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, * value. */ for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) { - if (USE_NEW_SCHEME(retry_counter)) { + /* + * An xHCI controller cannot send any packets to a device until + * a set address command successfully completes. + */ + if (USE_NEW_SCHEME(retry_counter) && !(hcd->driver->flags & HCD_USB3)) { struct usb_device_descriptor *buf; int r = 0; @@ -2619,7 +2642,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, * unauthorized address in the Connect Ack sequence; * authorization will assign the final address. */ - if (udev->wusb == 0) { + if (udev->wusb == 0) { for (j = 0; j < SET_ADDRESS_TRIES; ++j) { retval = hub_set_address(udev, devnum); if (retval >= 0) @@ -2632,13 +2655,20 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, devnum, retval); goto fail; } + if (udev->speed == USB_SPEED_SUPER) { + devnum = udev->devnum; + dev_info(&udev->dev, + "%s SuperSpeed USB device using %s and address %d\n", + (udev->config) ? "reset" : "new", + udev->bus->controller->driver->name, devnum); + } /* cope with hardware quirkiness: * - let SET_ADDRESS settle, some device hardware wants it * - read ep0 maxpacket even for high and low speed, */ msleep(10); - if (USE_NEW_SCHEME(retry_counter)) + if (USE_NEW_SCHEME(retry_counter) && !(hcd->driver->flags & HCD_USB3)) break; } @@ -2877,13 +2907,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, udev->level = hdev->level + 1; udev->wusb = hub_is_wusb(hub); - /* set the address */ - choose_address(udev); - if (udev->devnum <= 0) { - status = -ENOTCONN; /* Don't retry */ - goto loop; - } - /* * USB 3.0 devices are reset automatically before the connect * port status change appears, and the root hub port status @@ -2901,6 +2924,19 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, else udev->speed = USB_SPEED_UNKNOWN; + /* + * xHCI needs to issue an address device command later + * in the hub_port_init sequence for SS/HS/FS/LS devices. + */ + if (!(hcd->driver->flags & HCD_USB3)) { + /* set the address */ + choose_address(udev); + if (udev->devnum <= 0) { + status = -ENOTCONN; /* Don't retry */ + goto loop; + } + } + /* reset (non-USB 3.0 devices) and get descriptor */ status = hub_port_init(hub, udev, port1, i); if (status < 0) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index f026991d0bdf..55b8d3a22d26 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -184,11 +184,16 @@ EXPORT_SYMBOL_GPL(usb_find_interface); static void usb_release_dev(struct device *dev) { struct usb_device *udev; + struct usb_hcd *hcd; udev = to_usb_device(dev); + hcd = bus_to_hcd(udev->bus); usb_destroy_configuration(udev); - usb_put_hcd(bus_to_hcd(udev->bus)); + /* Root hubs aren't real devices, so don't free HCD resources */ + if (hcd->driver->free_dev && udev->parent) + hcd->driver->free_dev(hcd, udev); + usb_put_hcd(hcd); kfree(udev->product); kfree(udev->manufacturer); kfree(udev->serial); @@ -348,6 +353,13 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, kfree(dev); return NULL; } + /* Root hubs aren't true devices, so don't allocate HCD resources */ + if (usb_hcd->driver->alloc_dev && parent && + !usb_hcd->driver->alloc_dev(usb_hcd, dev)) { + usb_put_hcd(bus_to_hcd(bus)); + kfree(dev); + return NULL; + } device_initialize(&dev->dev); dev->dev.bus = &usb_bus_type; diff --git a/include/linux/usb.h b/include/linux/usb.h index 2b380a16c62f..475cb75cc378 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -420,6 +420,7 @@ struct usb_tt; * @skip_sys_resume: skip the next system resume * @wusb_dev: if this is a Wireless USB device, link to the WUSB * specific data for the device. + * @slot_id: Slot ID assigned by xHCI * * Notes: * Usbcore drivers should not set usbdev->state directly. Instead use @@ -504,6 +505,7 @@ struct usb_device { unsigned skip_sys_resume:1; #endif struct wusb_dev *wusb_dev; + int slot_id; }; #define to_usb_device(d) container_of(d, struct usb_device, dev) -- cgit v1.2.3 From 3ffbba9511b4148cbe1f6b6238686adaeaca8feb Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:57:38 -0700 Subject: USB: xhci: Allocate and address USB devices xHCI needs to get a "Slot ID" from the host controller and allocate other data structures for every USB device. Make usb_alloc_dev() and usb_release_dev() allocate and free these device structures. After setting up the xHC device structures, usb_alloc_dev() must wait for the hardware to respond to an Enable Slot command. usb_alloc_dev() fires off a Disable Slot command and does not wait for it to complete. When the USB core wants to choose an address for the device, the xHCI driver must issue a Set Address command and wait for an event for that command. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-dbg.c | 79 +++++++++++++++++ drivers/usb/host/xhci-hcd.c | 201 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-mem.c | 204 +++++++++++++++++++++++++++++++++++++++++-- drivers/usb/host/xhci-pci.c | 7 ++ drivers/usb/host/xhci-ring.c | 34 +++++++- drivers/usb/host/xhci.h | 94 +++++++++++++++----- 6 files changed, 590 insertions(+), 29 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 570cd4820458..16ef42a0fe85 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -410,3 +410,82 @@ void xhci_dbg_cmd_ptrs(struct xhci_hcd *xhci) val = xhci_readl(xhci, &xhci->op_regs->cmd_ring[1]); xhci_dbg(xhci, "// xHC command ring deq ptr high bits = 0x%x\n", val); } + +void xhci_dbg_ctx(struct xhci_hcd *xhci, struct xhci_device_control *ctx, dma_addr_t dma, unsigned int last_ep) +{ + int i, j; + int last_ep_ctx = 31; + /* Fields are 32 bits wide, DMA addresses are in bytes */ + int field_size = 32 / 8; + + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - drop flags\n", + (unsigned int) &ctx->drop_flags, + dma, ctx->drop_flags); + dma += field_size; + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - add flags\n", + (unsigned int) &ctx->add_flags, + dma, ctx->add_flags); + dma += field_size; + for (i = 0; i > 6; ++i) { + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - rsvd[%d]\n", + (unsigned int) &ctx->rsvd[i], + dma, ctx->rsvd[i], i); + dma += field_size; + } + + xhci_dbg(xhci, "Slot Context:\n"); + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - dev_info\n", + (unsigned int) &ctx->slot.dev_info, + dma, ctx->slot.dev_info); + dma += field_size; + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - dev_info2\n", + (unsigned int) &ctx->slot.dev_info2, + dma, ctx->slot.dev_info2); + dma += field_size; + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - tt_info\n", + (unsigned int) &ctx->slot.tt_info, + dma, ctx->slot.tt_info); + dma += field_size; + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - dev_state\n", + (unsigned int) &ctx->slot.dev_state, + dma, ctx->slot.dev_state); + dma += field_size; + for (i = 0; i > 4; ++i) { + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - rsvd[%d]\n", + (unsigned int) &ctx->slot.reserved[i], + dma, ctx->slot.reserved[i], i); + dma += field_size; + } + + if (last_ep < 31) + last_ep_ctx = last_ep + 1; + for (i = 0; i < last_ep_ctx; ++i) { + xhci_dbg(xhci, "Endpoint %02d Context:\n", i); + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - ep_info\n", + (unsigned int) &ctx->ep[i].ep_info, + dma, ctx->ep[i].ep_info); + dma += field_size; + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - ep_info2\n", + (unsigned int) &ctx->ep[i].ep_info2, + dma, ctx->ep[i].ep_info2); + dma += field_size; + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - deq[0]\n", + (unsigned int) &ctx->ep[i].deq[0], + dma, ctx->ep[i].deq[0]); + dma += field_size; + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - deq[1]\n", + (unsigned int) &ctx->ep[i].deq[1], + dma, ctx->ep[i].deq[1]); + dma += field_size; + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - tx_info\n", + (unsigned int) &ctx->ep[i].tx_info, + dma, ctx->ep[i].tx_info); + dma += field_size; + for (j = 0; j < 3; ++j) { + xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - rsvd[%d]\n", + (unsigned int) &ctx->ep[i].reserved[j], + dma, ctx->ep[i].reserved[j], j); + dma += field_size; + } + } +} diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index d7c2fed55978..a01d2ee7435a 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -318,6 +318,16 @@ void event_ring_work(unsigned long arg) xhci_debug_segment(xhci, xhci->cmd_ring->deq_seg); xhci_dbg_ring_ptrs(xhci, xhci->cmd_ring); xhci_dbg_cmd_ptrs(xhci); + for (i = 0; i < MAX_HC_SLOTS; ++i) { + if (xhci->devs[i]) { + for (j = 0; j < 31; ++j) { + if (xhci->devs[i]->ep_rings[j]) { + xhci_dbg(xhci, "Dev %d endpoint ring %d:\n", i, j); + xhci_debug_segment(xhci, xhci->devs[i]->ep_rings[j]->deq_seg); + } + } + } + } if (xhci->noops_submitted != NUM_TEST_NOOPS) if (setup_one_noop(xhci)) @@ -499,6 +509,197 @@ void xhci_shutdown(struct usb_hcd *hcd) /*-------------------------------------------------------------------------*/ +/* + * At this point, the struct usb_device is about to go away, the device has + * disconnected, and all traffic has been stopped and the endpoints have been + * disabled. Free any HC data structures associated with that device. + */ +void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + unsigned long flags; + + if (udev->slot_id == 0) + return; + + spin_lock_irqsave(&xhci->lock, flags); + if (queue_slot_control(xhci, TRB_DISABLE_SLOT, udev->slot_id)) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); + return; + } + ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + /* + * Event command completion handler will free any data structures + * associated with the slot + */ +} + +/* + * Returns 0 if the xHC ran out of device slots, the Enable Slot command + * timed out, or allocating memory failed. Returns 1 on success. + */ +int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + unsigned long flags; + int timeleft; + int ret; + + spin_lock_irqsave(&xhci->lock, flags); + ret = queue_slot_control(xhci, TRB_ENABLE_SLOT, 0); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); + return 0; + } + ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* XXX: how much time for xHC slot assignment? */ + timeleft = wait_for_completion_interruptible_timeout(&xhci->addr_dev, + USB_CTRL_SET_TIMEOUT); + if (timeleft <= 0) { + xhci_warn(xhci, "%s while waiting for a slot\n", + timeleft == 0 ? "Timeout" : "Signal"); + /* FIXME cancel the enable slot request */ + return 0; + } + + spin_lock_irqsave(&xhci->lock, flags); + if (!xhci->slot_id) { + xhci_err(xhci, "Error while assigning device slot ID\n"); + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; + } + if (!xhci_alloc_virt_device(xhci, xhci->slot_id, udev, GFP_KERNEL)) { + /* Disable slot, if we can do it without mem alloc */ + xhci_warn(xhci, "Could not allocate xHCI USB device data structures\n"); + if (!queue_slot_control(xhci, TRB_DISABLE_SLOT, udev->slot_id)) + ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; + } + udev->slot_id = xhci->slot_id; + /* Is this a LS or FS device under a HS hub? */ + /* Hub or peripherial? */ + spin_unlock_irqrestore(&xhci->lock, flags); + return 1; +} + +/* + * Issue an Address Device command (which will issue a SetAddress request to + * the device). + * We should be protected by the usb_address0_mutex in khubd's hub_port_init, so + * we should only issue and wait on one address command at the same time. + * + * We add one to the device address issued by the hardware because the USB core + * uses address 1 for the root hubs (even though they're not really devices). + */ +int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + unsigned long flags; + int timeleft; + struct xhci_virt_device *virt_dev; + int ret = 0; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + u32 temp; + + if (!udev->slot_id) { + xhci_dbg(xhci, "Bad Slot ID %d\n", udev->slot_id); + return -EINVAL; + } + + spin_lock_irqsave(&xhci->lock, flags); + virt_dev = xhci->devs[udev->slot_id]; + + /* If this is a Set Address to an unconfigured device, setup ep 0 */ + if (!udev->config) + xhci_setup_addressable_virt_dev(xhci, udev); + /* Otherwise, assume the core has the device configured how it wants */ + + ret = queue_address_device(xhci, virt_dev->in_ctx_dma, udev->slot_id); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); + return ret; + } + ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* ctrl tx can take up to 5 sec; XXX: need more time for xHC? */ + timeleft = wait_for_completion_interruptible_timeout(&xhci->addr_dev, + USB_CTRL_SET_TIMEOUT); + /* FIXME: From section 4.3.4: "Software shall be responsible for timing + * the SetAddress() "recovery interval" required by USB and aborting the + * command on a timeout. + */ + if (timeleft <= 0) { + xhci_warn(xhci, "%s while waiting for a slot\n", + timeleft == 0 ? "Timeout" : "Signal"); + /* FIXME cancel the address device command */ + return -ETIME; + } + + spin_lock_irqsave(&xhci->lock, flags); + switch (virt_dev->cmd_status) { + case COMP_CTX_STATE: + case COMP_EBADSLT: + xhci_err(xhci, "Setup ERROR: address device command for slot %d.\n", + udev->slot_id); + ret = -EINVAL; + break; + case COMP_TX_ERR: + dev_warn(&udev->dev, "Device not responding to set address.\n"); + ret = -EPROTO; + break; + case COMP_SUCCESS: + xhci_dbg(xhci, "Successful Address Device command\n"); + break; + default: + xhci_err(xhci, "ERROR: unexpected command completion " + "code 0x%x.\n", virt_dev->cmd_status); + ret = -EINVAL; + break; + } + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + return ret; + } + temp = xhci_readl(xhci, &xhci->op_regs->dcbaa_ptr[0]); + xhci_dbg(xhci, "Op regs DCBAA ptr[0] = %#08x\n", temp); + temp = xhci_readl(xhci, &xhci->op_regs->dcbaa_ptr[1]); + xhci_dbg(xhci, "Op regs DCBAA ptr[1] = %#08x\n", temp); + xhci_dbg(xhci, "Slot ID %d dcbaa entry[0] @%08x = %#08x\n", + udev->slot_id, + (unsigned int) &xhci->dcbaa->dev_context_ptrs[2*udev->slot_id], + xhci->dcbaa->dev_context_ptrs[2*udev->slot_id]); + xhci_dbg(xhci, "Slot ID %d dcbaa entry[1] @%08x = %#08x\n", + udev->slot_id, + (unsigned int) &xhci->dcbaa->dev_context_ptrs[2*udev->slot_id+1], + xhci->dcbaa->dev_context_ptrs[2*udev->slot_id+1]); + xhci_dbg(xhci, "Output Context DMA address = %#08x\n", + virt_dev->out_ctx_dma); + xhci_dbg(xhci, "Slot ID %d Input Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, virt_dev->in_ctx, virt_dev->in_ctx_dma, 2); + xhci_dbg(xhci, "Slot ID %d Output Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, virt_dev->out_ctx, virt_dev->out_ctx_dma, 2); + /* + * USB core uses address 1 for the roothubs, so we add one to the + * address given back to us by the HC. + */ + udev->devnum = (virt_dev->out_ctx->slot.dev_state & DEV_ADDR_MASK) + 1; + /* FIXME: Zero the input context control for later use? */ + spin_unlock_irqrestore(&xhci->lock, flags); + + xhci_dbg(xhci, "Device address = %d\n", udev->devnum); + /* XXX Meh, not sure if anyone else but choose_address uses this. */ + set_bit(udev->devnum, udev->bus->devmap.devicemap); + + return 0; +} + int xhci_get_frame(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 005d44641d81..d34b91a135a1 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -188,12 +188,187 @@ fail: return 0; } +void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id) +{ + struct xhci_virt_device *dev; + int i; + + /* Slot ID 0 is reserved */ + if (slot_id == 0 || !xhci->devs[slot_id]) + return; + + dev = xhci->devs[slot_id]; + xhci->dcbaa->dev_context_ptrs[2*slot_id] = 0; + xhci->dcbaa->dev_context_ptrs[2*slot_id + 1] = 0; + if (!dev) + return; + + for (i = 0; i < 31; ++i) + if (dev->ep_rings[i]) + xhci_ring_free(xhci, dev->ep_rings[i]); + + if (dev->in_ctx) + dma_pool_free(xhci->device_pool, + dev->in_ctx, dev->in_ctx_dma); + if (dev->out_ctx) + dma_pool_free(xhci->device_pool, + dev->out_ctx, dev->out_ctx_dma); + kfree(xhci->devs[slot_id]); + xhci->devs[slot_id] = 0; +} + +int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, + struct usb_device *udev, gfp_t flags) +{ + dma_addr_t dma; + struct xhci_virt_device *dev; + + /* Slot ID 0 is reserved */ + if (slot_id == 0 || xhci->devs[slot_id]) { + xhci_warn(xhci, "Bad Slot ID %d\n", slot_id); + return 0; + } + + xhci->devs[slot_id] = kzalloc(sizeof(*xhci->devs[slot_id]), flags); + if (!xhci->devs[slot_id]) + return 0; + dev = xhci->devs[slot_id]; + + /* Allocate the (output) device context that will be used in the HC */ + dev->out_ctx = dma_pool_alloc(xhci->device_pool, flags, &dma); + if (!dev->out_ctx) + goto fail; + dev->out_ctx_dma = dma; + xhci_dbg(xhci, "Slot %d output ctx = 0x%x (dma)\n", slot_id, dma); + memset(dev->out_ctx, 0, sizeof(*dev->out_ctx)); + + /* Allocate the (input) device context for address device command */ + dev->in_ctx = dma_pool_alloc(xhci->device_pool, flags, &dma); + if (!dev->in_ctx) + goto fail; + dev->in_ctx_dma = dma; + xhci_dbg(xhci, "Slot %d input ctx = 0x%x (dma)\n", slot_id, dma); + memset(dev->in_ctx, 0, sizeof(*dev->in_ctx)); + + /* Allocate endpoint 0 ring */ + dev->ep_rings[0] = xhci_ring_alloc(xhci, 1, true, flags); + if (!dev->ep_rings[0]) + goto fail; + + /* + * Point to output device context in dcbaa; skip the output control + * context, which is eight 32 bit fields (or 32 bytes long) + */ + xhci->dcbaa->dev_context_ptrs[2*slot_id] = + (u32) dev->out_ctx_dma + (32); + xhci_dbg(xhci, "Set slot id %d dcbaa entry 0x%x to 0x%x\n", + slot_id, + (unsigned int) &xhci->dcbaa->dev_context_ptrs[2*slot_id], + dev->out_ctx_dma); + xhci->dcbaa->dev_context_ptrs[2*slot_id + 1] = 0; + + return 1; +fail: + xhci_free_virt_device(xhci, slot_id); + return 0; +} + +/* Setup an xHCI virtual device for a Set Address command */ +int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *udev) +{ + struct xhci_virt_device *dev; + struct xhci_ep_ctx *ep0_ctx; + struct usb_device *top_dev; + + dev = xhci->devs[udev->slot_id]; + /* Slot ID 0 is reserved */ + if (udev->slot_id == 0 || !dev) { + xhci_warn(xhci, "Slot ID %d is not assigned to this device\n", + udev->slot_id); + return -EINVAL; + } + ep0_ctx = &dev->in_ctx->ep[0]; + + /* 2) New slot context and endpoint 0 context are valid*/ + dev->in_ctx->add_flags = SLOT_FLAG | EP0_FLAG; + + /* 3) Only the control endpoint is valid - one endpoint context */ + dev->in_ctx->slot.dev_info |= LAST_CTX(1); + + switch (udev->speed) { + case USB_SPEED_SUPER: + dev->in_ctx->slot.dev_info |= (u32) udev->route; + dev->in_ctx->slot.dev_info |= (u32) SLOT_SPEED_SS; + break; + case USB_SPEED_HIGH: + dev->in_ctx->slot.dev_info |= (u32) SLOT_SPEED_HS; + break; + case USB_SPEED_FULL: + dev->in_ctx->slot.dev_info |= (u32) SLOT_SPEED_FS; + break; + case USB_SPEED_LOW: + dev->in_ctx->slot.dev_info |= (u32) SLOT_SPEED_LS; + break; + case USB_SPEED_VARIABLE: + xhci_dbg(xhci, "FIXME xHCI doesn't support wireless speeds\n"); + return -EINVAL; + break; + default: + /* Speed was set earlier, this shouldn't happen. */ + BUG(); + } + /* Find the root hub port this device is under */ + for (top_dev = udev; top_dev->parent && top_dev->parent->parent; + top_dev = top_dev->parent) + /* Found device below root hub */; + dev->in_ctx->slot.dev_info2 |= (u32) ROOT_HUB_PORT(top_dev->portnum); + xhci_dbg(xhci, "Set root hub portnum to %d\n", top_dev->portnum); + + /* Is this a LS/FS device under a HS hub? */ + /* + * FIXME: I don't think this is right, where does the TT info for the + * roothub or parent hub come from? + */ + if ((udev->speed == USB_SPEED_LOW || udev->speed == USB_SPEED_FULL) && + udev->tt) { + dev->in_ctx->slot.tt_info = udev->tt->hub->slot_id; + dev->in_ctx->slot.tt_info |= udev->ttport << 8; + } + xhci_dbg(xhci, "udev->tt = 0x%x\n", (unsigned int) udev->tt); + xhci_dbg(xhci, "udev->ttport = 0x%x\n", udev->ttport); + + /* Step 4 - ring already allocated */ + /* Step 5 */ + ep0_ctx->ep_info2 = EP_TYPE(CTRL_EP); + /* + * See section 4.3 bullet 6: + * The default Max Packet size for ep0 is "8 bytes for a USB2 + * LS/FS/HS device or 512 bytes for a USB3 SS device" + * XXX: Not sure about wireless USB devices. + */ + if (udev->speed == USB_SPEED_SUPER) + ep0_ctx->ep_info2 |= MAX_PACKET(512); + else + ep0_ctx->ep_info2 |= MAX_PACKET(8); + /* EP 0 can handle "burst" sizes of 1, so Max Burst Size field is 0 */ + ep0_ctx->ep_info2 |= MAX_BURST(0); + ep0_ctx->ep_info2 |= ERROR_COUNT(3); + + ep0_ctx->deq[0] = + dev->ep_rings[0]->first_seg->dma; + ep0_ctx->deq[0] |= dev->ep_rings[0]->cycle_state; + ep0_ctx->deq[1] = 0; + + /* Steps 7 and 8 were done in xhci_alloc_virt_device() */ + + return 0; +} + void xhci_mem_cleanup(struct xhci_hcd *xhci) { struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); int size; - - /* XXX: Free all the segments in the various rings */ + int i; /* Free the Event Ring Segment Table and the actual Event Ring */ xhci_writel(xhci, 0, &xhci->ir_set->erst_size); @@ -218,16 +393,27 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci_ring_free(xhci, xhci->cmd_ring); xhci->cmd_ring = NULL; xhci_dbg(xhci, "Freed command ring\n"); + + for (i = 1; i < MAX_HC_SLOTS; ++i) + xhci_free_virt_device(xhci, i); + if (xhci->segment_pool) dma_pool_destroy(xhci->segment_pool); xhci->segment_pool = NULL; xhci_dbg(xhci, "Freed segment pool\n"); + + if (xhci->device_pool) + dma_pool_destroy(xhci->device_pool); + xhci->device_pool = NULL; + xhci_dbg(xhci, "Freed device context pool\n"); + xhci_writel(xhci, 0, &xhci->op_regs->dcbaa_ptr[1]); xhci_writel(xhci, 0, &xhci->op_regs->dcbaa_ptr[0]); if (xhci->dcbaa) pci_free_consistent(pdev, sizeof(*xhci->dcbaa), xhci->dcbaa, xhci->dcbaa->dma); xhci->dcbaa = NULL; + xhci->page_size = 0; xhci->page_shift = 0; } @@ -280,8 +466,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) goto fail; memset(xhci->dcbaa, 0, sizeof *(xhci->dcbaa)); xhci->dcbaa->dma = dma; - xhci_dbg(xhci, "// Setting device context base array address to 0x%x\n", - xhci->dcbaa->dma); + xhci_dbg(xhci, "// Device context base array address = 0x%x (DMA), 0x%x (virt)\n", + xhci->dcbaa->dma, (unsigned int) xhci->dcbaa); xhci_writel(xhci, (u32) 0, &xhci->op_regs->dcbaa_ptr[1]); xhci_writel(xhci, dma, &xhci->op_regs->dcbaa_ptr[0]); @@ -293,7 +479,12 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) */ xhci->segment_pool = dma_pool_create("xHCI ring segments", dev, SEGMENT_SIZE, 64, xhci->page_size); - if (!xhci->segment_pool) + /* See Table 46 and Note on Figure 55 */ + /* FIXME support 64-byte contexts */ + xhci->device_pool = dma_pool_create("xHCI input/output contexts", dev, + sizeof(struct xhci_device_control), + 64, xhci->page_size); + if (!xhci->segment_pool || !xhci->device_pool) goto fail; /* Set up the command ring to have one segments for now. */ @@ -385,6 +576,9 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) * something other than the default (~1ms minimum between interrupts). * See section 5.5.1.2. */ + init_completion(&xhci->addr_dev); + for (i = 0; i < MAX_HC_SLOTS; ++i) + xhci->devs[i] = 0; return 0; fail: diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 005c5b264a7c..7ac12b4ffe86 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -108,6 +108,13 @@ static const struct hc_driver xhci_pci_hc_driver = { .stop = xhci_stop, .shutdown = xhci_shutdown, + /* + * managing i/o requests and associated device resources + */ + .alloc_dev = xhci_alloc_dev, + .free_dev = xhci_free_dev, + .address_device = xhci_address_device, + /* * scheduling support */ diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 9d6bb3d730c4..901ce70b30b8 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -252,13 +252,10 @@ void ring_cmd_db(struct xhci_hcd *xhci) static void handle_cmd_completion(struct xhci_hcd *xhci, struct xhci_event_cmd *event) { + int slot_id = TRB_TO_SLOT_ID(event->flags); u64 cmd_dma; dma_addr_t cmd_dequeue_dma; - /* Check completion code */ - if (GET_COMP_CODE(event->status) != COMP_SUCCESS) - xhci_dbg(xhci, "WARN: unsuccessful no-op command\n"); - cmd_dma = (((u64) event->cmd_trb[1]) << 32) + event->cmd_trb[0]; cmd_dequeue_dma = trb_virt_to_dma(xhci->cmd_ring->deq_seg, xhci->cmd_ring->dequeue); @@ -273,6 +270,21 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, return; } switch (xhci->cmd_ring->dequeue->generic.field[3] & TRB_TYPE_BITMASK) { + case TRB_TYPE(TRB_ENABLE_SLOT): + if (GET_COMP_CODE(event->status) == COMP_SUCCESS) + xhci->slot_id = slot_id; + else + xhci->slot_id = 0; + complete(&xhci->addr_dev); + break; + case TRB_TYPE(TRB_DISABLE_SLOT): + if (xhci->devs[slot_id]) + xhci_free_virt_device(xhci, slot_id); + break; + case TRB_TYPE(TRB_ADDR_DEV): + xhci->devs[slot_id]->cmd_status = GET_COMP_CODE(event->status); + complete(&xhci->addr_dev); + break; case TRB_TYPE(TRB_CMD_NOOP): ++xhci->noops_handled; break; @@ -400,3 +412,17 @@ void *setup_one_noop(struct xhci_hcd *xhci) xhci->noops_submitted++; return ring_cmd_db; } + +/* Queue a slot enable or disable request on the command ring */ +int queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id) +{ + return queue_command(xhci, 0, 0, 0, + TRB_TYPE(trb_type) | SLOT_ID_FOR_TRB(slot_id)); +} + +/* Queue an address device command TRB */ +int queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id) +{ + return queue_command(xhci, in_ctx_ptr, 0, 0, + TRB_TYPE(TRB_ADDR_DEV) | SLOT_ID_FOR_TRB(slot_id)); +} diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 059c659d3f39..4ef6b9e88504 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -285,12 +285,21 @@ struct xhci_op_regs { * 4 - super speed * 5-15 reserved */ -#define DEV_SPEED_MASK (0xf<<10) +#define DEV_SPEED_MASK (0xf << 10) +#define XDEV_FS (0x1 << 10) +#define XDEV_LS (0x2 << 10) +#define XDEV_HS (0x3 << 10) +#define XDEV_SS (0x4 << 10) #define DEV_UNDEFSPEED(p) (((p) & DEV_SPEED_MASK) == (0x0<<10)) -#define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == (0x1<<10)) -#define DEV_LOWSPEED(p) (((p) & DEV_SPEED_MASK) == (0x2<<10)) -#define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == (0x3<<10)) -#define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == (0x4<<10)) +#define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_FS) +#define DEV_LOWSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_LS) +#define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_HS) +#define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_SS) +/* Bits 20:23 in the Slot Context are the speed for the device */ +#define SLOT_SPEED_FS (XDEV_FS << 10) +#define SLOT_SPEED_LS (XDEV_LS << 10) +#define SLOT_SPEED_HS (XDEV_HS << 10) +#define SLOT_SPEED_SS (XDEV_SS << 10) /* Port Indicator Control */ #define PORT_LED_OFF (0 << 14) #define PORT_LED_AMBER (1 << 14) @@ -471,14 +480,19 @@ struct xhci_slot_ctx { /* Set if the device is a hub - bit 26 */ #define DEV_HUB (0x1 << 26) /* Index of the last valid endpoint context in this device context - 27:31 */ -#define LAST_EP_MASK (0x1f << 27) -#define LAST_EP(p) ((p) << 27) +#define LAST_CTX_MASK (0x1f << 27) +#define LAST_CTX(p) ((p) << 27) +#define LAST_CTX_TO_EP_NUM(p) (((p) >> 27) - 1) +/* Plus one for the slot context flag */ +#define EPI_TO_FLAG(p) (1 << ((p) + 1)) +#define SLOT_FLAG (1 << 0) +#define EP0_FLAG (1 << 1) /* dev_info2 bitmasks */ /* Max Exit Latency (ms) - worst case time to wake up all links in dev path */ #define MAX_EXIT (0xffff) /* Root hub port number that is needed to access the USB device */ -#define ROOT_HUB_PORT (0xff << 16) +#define ROOT_HUB_PORT(p) (((p) & 0xff) << 16) /* tt_info bitmasks */ /* @@ -495,7 +509,7 @@ struct xhci_slot_ctx { /* dev_state bitmasks */ /* USB device address - assigned by the HC */ -#define DEV_ADDR (0xff) +#define DEV_ADDR_MASK (0xff) /* bits 8:26 reserved */ /* Slot state */ #define SLOT_STATE (0x1f << 27) @@ -507,12 +521,13 @@ struct xhci_slot_ctx { * @ep_info2: information on endpoint type, max packet size, max burst size, * error count, and whether the HC will force an event for all * transactions. - * @ep_ring: 64-bit ring address. If the endpoint only defines one flow, - * this points to the endpoint transfer ring. Otherwise, it points - * to a flow context array, which has a ring pointer for each flow. - * @intr_target: - * 64-bit address of the Interrupter Target that will receive - * events from this endpoint. + * @deq: 64-bit ring dequeue pointer address. If the endpoint only + * defines one stream, this points to the endpoint transfer ring. + * Otherwise, it points to a stream context array, which has a + * ring pointer for each flow. + * @tx_info: + * Average TRB lengths for the endpoint ring and + * max payload within an Endpoint Service Interval Time (ESIT). * * Endpoint Context - section 6.2.1.2. This assumes the HC uses 32-byte context * structures. If the HC uses 64-byte contexts, there is an additional 32 bytes @@ -521,12 +536,10 @@ struct xhci_slot_ctx { struct xhci_ep_ctx { u32 ep_info; u32 ep_info2; - /* 64-bit endpoint ring address */ - u32 ep_ring[2]; - /* 64-bit address of the interrupter target */ - u32 intr_target[2]; + u32 deq[2]; + u32 tx_info; /* offset 0x14 - 0x1f reserved for HC internal use */ - u32 reserved[2]; + u32 reserved[3]; } __attribute__ ((packed)); /* ep_info bitmasks */ @@ -589,6 +602,28 @@ struct xhci_device_control { #define ADD_EP(x) (0x1 << x) +struct xhci_virt_device { + /* + * Commands to the hardware are passed an "input context" that + * tells the hardware what to change in its data structures. + * The hardware will return changes in an "output context" that + * software must allocate for the hardware. We need to keep + * track of input and output contexts separately because + * these commands might fail and we don't trust the hardware. + */ + struct xhci_device_control *out_ctx; + dma_addr_t out_ctx_dma; + /* Used for addressing devices and configuration changes */ + struct xhci_device_control *in_ctx; + dma_addr_t in_ctx_dma; + /* FIXME when stream support is added */ + struct xhci_ring *ep_rings[31]; + dma_addr_t ep_dma[31]; + /* Status of the last command issued for this device */ + u32 cmd_status; +}; + + /** * struct xhci_device_context_array * @dev_context_ptr array of 64-bit DMA addresses for device contexts @@ -711,6 +746,11 @@ struct xhci_event_cmd { u32 flags; } __attribute__ ((packed)); +/* flags bitmasks */ +/* bits 16:23 are the virtual function ID */ +/* bits 24:31 are the slot ID */ +#define TRB_TO_SLOT_ID(p) (((p) & (0xff<<24)) >> 24) +#define SLOT_ID_FOR_TRB(p) (((p) & 0xff) << 24) /* Port Status Change Event TRB fields */ /* Port ID - bits 31:24 */ @@ -931,6 +971,11 @@ struct xhci_hcd { struct xhci_ring *cmd_ring; struct xhci_ring *event_ring; struct xhci_erst erst; + /* slot enabling and address device helpers */ + struct completion addr_dev; + int slot_id; + /* Internal mirror of the HW's dcbaa */ + struct xhci_virt_device *devs[MAX_HC_SLOTS]; /* DMA pools */ struct dma_pool *device_pool; @@ -1002,10 +1047,14 @@ void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring); void xhci_dbg_erst(struct xhci_hcd *xhci, struct xhci_erst *erst); void xhci_dbg_cmd_ptrs(struct xhci_hcd *xhci); void xhci_dbg_ring_ptrs(struct xhci_hcd *xhci, struct xhci_ring *ring); +void xhci_dbg_ctx(struct xhci_hcd *xhci, struct xhci_device_control *ctx, dma_addr_t dma, unsigned int last_ep); /* xHCI memory managment */ void xhci_mem_cleanup(struct xhci_hcd *xhci); int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags); +void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id); +int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, struct usb_device *udev, gfp_t flags); +int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *udev); #ifdef CONFIG_PCI /* xHCI PCI glue */ @@ -1022,6 +1071,9 @@ void xhci_stop(struct usb_hcd *hcd); void xhci_shutdown(struct usb_hcd *hcd); int xhci_get_frame(struct usb_hcd *hcd); irqreturn_t xhci_irq(struct usb_hcd *hcd); +int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev); +void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev); +int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev); /* xHCI ring, segment, TRB, and TD functions */ dma_addr_t trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); @@ -1029,6 +1081,8 @@ void ring_cmd_db(struct xhci_hcd *xhci); void *setup_one_noop(struct xhci_hcd *xhci); void handle_event(struct xhci_hcd *xhci); void set_hc_event_deq(struct xhci_hcd *xhci); +int queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id); +int queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); /* xHCI roothub code */ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, -- cgit v1.2.3 From d0e96f5a71a032ced0c35f521c1cbd67e816922a Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:58:01 -0700 Subject: USB: xhci: Control transfer support. Allow device drivers to enqueue URBs to control endpoints on devices under an xHCI host controller. Each control transfer is represented by a series of Transfer Descriptors (TDs) written to an endpoint ring. There is one TD for the Setup phase, (optionally) one TD for the Data phase, and one TD for the Status phase. Enqueue these TDs onto the endpoint ring that represents the control endpoint. The host controller hardware will return an event on the event ring that points to the (DMA) address of one of the TDs on the endpoint ring. If the transfer was successful, the transfer event TRB will have a completion code of success, and it will point to the Status phase TD. Anything else is considered an error. This should work for control endpoints besides the default endpoint, but that hasn't been tested. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-hcd.c | 93 +++++++++++ drivers/usb/host/xhci-mem.c | 2 + drivers/usb/host/xhci-pci.c | 2 + drivers/usb/host/xhci-ring.c | 383 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci.h | 29 +++- 5 files changed, 506 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index a01d2ee7435a..5d94b4ffac92 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -509,6 +509,99 @@ void xhci_shutdown(struct usb_hcd *hcd) /*-------------------------------------------------------------------------*/ +/** + * xhci_get_endpoint_index - Used for passing endpoint bitmasks between the core and + * HCDs. Find the index for an endpoint given its descriptor. Use the return + * value to right shift 1 for the bitmask. + * + * Index = (epnum * 2) + direction - 1, + * where direction = 0 for OUT, 1 for IN. + * For control endpoints, the IN index is used (OUT index is unused), so + * index = (epnum * 2) + direction - 1 = (epnum * 2) + 1 - 1 = (epnum * 2) + */ +unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc) +{ + unsigned int index; + if (usb_endpoint_xfer_control(desc)) + index = (unsigned int) (usb_endpoint_num(desc)*2); + else + index = (unsigned int) (usb_endpoint_num(desc)*2) + + (usb_endpoint_dir_in(desc) ? 1 : 0) - 1; + return index; +} + +/* Returns 1 if the arguments are OK; + * returns 0 this is a root hub; returns -EINVAL for NULL pointers. + */ +int xhci_check_args(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep, int check_ep, const char *func) { + if (!hcd || (check_ep && !ep) || !udev) { + printk(KERN_DEBUG "xHCI %s called with invalid args\n", + func); + return -EINVAL; + } + if (!udev->parent) { + printk(KERN_DEBUG "xHCI %s called for root hub\n", + func); + return 0; + } + if (!udev->slot_id) { + printk(KERN_DEBUG "xHCI %s called with unaddressed device\n", + func); + return -EINVAL; + } + return 1; +} + +/* + * non-error returns are a promise to giveback() the urb later + * we drop ownership so next owner (or urb unlink) can get it + */ +int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + unsigned long flags; + int ret = 0; + unsigned int slot_id, ep_index; + + if (!urb || xhci_check_args(hcd, urb->dev, urb->ep, true, __func__) <= 0) + return -EINVAL; + + slot_id = urb->dev->slot_id; + ep_index = xhci_get_endpoint_index(&urb->ep->desc); + /* Only support ep 0 control transfers for now */ + if (ep_index != 0) { + xhci_dbg(xhci, "WARN: urb submitted to unsupported ep %x\n", + urb->ep->desc.bEndpointAddress); + return -ENOSYS; + } + + spin_lock_irqsave(&xhci->lock, flags); + if (!xhci->devs || !xhci->devs[slot_id]) { + if (!in_interrupt()) + dev_warn(&urb->dev->dev, "WARN: urb submitted for dev with no Slot ID\n"); + return -EINVAL; + } + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + if (!in_interrupt()) + xhci_dbg(xhci, "urb submitted during PCI suspend\n"); + ret = -ESHUTDOWN; + goto exit; + } + ret = queue_ctrl_tx(xhci, mem_flags, urb, slot_id, ep_index); +exit: + spin_unlock_irqrestore(&xhci->lock, flags); + return ret; +} + +/* Remove from hardware lists + * completions normally happen asynchronously + */ +int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + return -ENOSYS; +} + /* * At this point, the struct usb_device is about to go away, the device has * disconnected, and all traffic has been stopped and the endpoints have been diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index d34b91a135a1..6ff2e298bff8 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -141,6 +141,7 @@ static struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, if (!ring) return 0; + INIT_LIST_HEAD(&ring->td_list); if (num_segs == 0) return ring; @@ -188,6 +189,7 @@ fail: return 0; } +/* All the xhci_tds in the ring's TD list should be freed at this point */ void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id) { struct xhci_virt_device *dev; diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 7ac12b4ffe86..ff9a4ef22338 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -111,6 +111,8 @@ static const struct hc_driver xhci_pci_hc_driver = { /* * managing i/o requests and associated device resources */ + .urb_enqueue = xhci_urb_enqueue, + .urb_dequeue = xhci_urb_dequeue, .alloc_dev = xhci_alloc_dev, .free_dev = xhci_free_dev, .address_device = xhci_address_device, diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 901ce70b30b8..f04162ae4374 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -320,6 +320,199 @@ static void handle_port_status(struct xhci_hcd *xhci, spin_lock(&xhci->lock); } +/* + * This TD is defined by the TRBs starting at start_trb in start_seg and ending + * at end_trb, which may be in another segment. If the suspect DMA address is a + * TRB in this TD, this function returns that TRB's segment. Otherwise it + * returns 0. + */ +static struct xhci_segment *trb_in_td( + struct xhci_segment *start_seg, + union xhci_trb *start_trb, + union xhci_trb *end_trb, + dma_addr_t suspect_dma) +{ + dma_addr_t start_dma; + dma_addr_t end_seg_dma; + dma_addr_t end_trb_dma; + struct xhci_segment *cur_seg; + + start_dma = trb_virt_to_dma(start_seg, start_trb); + cur_seg = start_seg; + + do { + /* + * Last TRB is a link TRB (unless we start inserting links in + * the middle, FIXME if you do) + */ + end_seg_dma = trb_virt_to_dma(cur_seg, &start_seg->trbs[TRBS_PER_SEGMENT - 2]); + /* If the end TRB isn't in this segment, this is set to 0 */ + end_trb_dma = trb_virt_to_dma(cur_seg, end_trb); + + if (end_trb_dma > 0) { + /* The end TRB is in this segment, so suspect should be here */ + if (start_dma <= end_trb_dma) { + if (suspect_dma >= start_dma && suspect_dma <= end_trb_dma) + return cur_seg; + } else { + /* Case for one segment with + * a TD wrapped around to the top + */ + if ((suspect_dma >= start_dma && + suspect_dma <= end_seg_dma) || + (suspect_dma >= cur_seg->dma && + suspect_dma <= end_trb_dma)) + return cur_seg; + } + return 0; + } else { + /* Might still be somewhere in this segment */ + if (suspect_dma >= start_dma && suspect_dma <= end_seg_dma) + return cur_seg; + } + cur_seg = cur_seg->next; + start_dma = trb_virt_to_dma(cur_seg, &cur_seg->trbs[0]); + } while (1); + +} + +/* + * If this function returns an error condition, it means it got a Transfer + * event with a corrupted Slot ID, Endpoint ID, or TRB DMA address. + * At this point, the host controller is probably hosed and should be reset. + */ +static int handle_tx_event(struct xhci_hcd *xhci, + struct xhci_transfer_event *event) +{ + struct xhci_virt_device *xdev; + struct xhci_ring *ep_ring; + int ep_index; + struct xhci_td *td = 0; + dma_addr_t event_dma; + struct xhci_segment *event_seg; + union xhci_trb *event_trb; + struct urb *urb = NULL; + int status = -EINPROGRESS; + + xdev = xhci->devs[TRB_TO_SLOT_ID(event->flags)]; + if (!xdev) { + xhci_err(xhci, "ERROR Transfer event pointed to bad slot\n"); + return -ENODEV; + } + + /* Endpoint ID is 1 based, our index is zero based */ + ep_index = TRB_TO_EP_ID(event->flags) - 1; + ep_ring = xdev->ep_rings[ep_index]; + if (!ep_ring || (xdev->out_ctx->ep[ep_index].ep_info & EP_STATE_MASK) == EP_STATE_DISABLED) { + xhci_err(xhci, "ERROR Transfer event pointed to disabled endpoint\n"); + return -ENODEV; + } + + event_dma = event->buffer[0]; + if (event->buffer[1] != 0) + xhci_warn(xhci, "WARN ignoring upper 32-bits of 64-bit TRB dma address\n"); + + /* This TRB should be in the TD at the head of this ring's TD list */ + if (list_empty(&ep_ring->td_list)) { + xhci_warn(xhci, "WARN Event TRB for slot %d ep %d with no TDs queued?\n", + TRB_TO_SLOT_ID(event->flags), ep_index); + xhci_dbg(xhci, "Event TRB with TRB type ID %u\n", + (unsigned int) (event->flags & TRB_TYPE_BITMASK)>>10); + xhci_print_trb_offsets(xhci, (union xhci_trb *) event); + urb = NULL; + goto cleanup; + } + td = list_entry(ep_ring->td_list.next, struct xhci_td, td_list); + + /* Is this a TRB in the currently executing TD? */ + event_seg = trb_in_td(ep_ring->deq_seg, ep_ring->dequeue, + td->last_trb, event_dma); + if (!event_seg) { + /* HC is busted, give up! */ + xhci_err(xhci, "ERROR Transfer event TRB DMA ptr not part of current TD\n"); + return -ESHUTDOWN; + } + event_trb = &event_seg->trbs[(event_dma - event_seg->dma) / sizeof(*event_trb)]; + + /* Now update the urb's actual_length and give back to the core */ + /* Was this a control transfer? */ + if (usb_endpoint_xfer_control(&td->urb->ep->desc)) { + xhci_debug_trb(xhci, xhci->event_ring->dequeue); + switch (GET_COMP_CODE(event->transfer_len)) { + case COMP_SUCCESS: + if (event_trb == ep_ring->dequeue) { + xhci_warn(xhci, "WARN: Success on ctrl setup TRB without IOC set??\n"); + status = -ESHUTDOWN; + } else if (event_trb != td->last_trb) { + xhci_warn(xhci, "WARN: Success on ctrl data TRB without IOC set??\n"); + status = -ESHUTDOWN; + } else { + xhci_dbg(xhci, "Successful control transfer!\n"); + status = 0; + } + break; + case COMP_SHORT_TX: + xhci_warn(xhci, "WARN: short transfer on control ep\n"); + status = -EREMOTEIO; + break; + case COMP_STALL: + xhci_warn(xhci, "WARN: Stalled control ep\n"); + status = -EPIPE; + break; + case COMP_TRB_ERR: + xhci_warn(xhci, "WARN: TRB error on control ep\n"); + status = -EILSEQ; + break; + case COMP_TX_ERR: + xhci_warn(xhci, "WARN: transfer error on control ep\n"); + status = -EPROTO; + break; + case COMP_DB_ERR: + xhci_warn(xhci, "WARN: HC couldn't access mem fast enough on control TX\n"); + status = -ENOSR; + break; + default: + xhci_dbg(xhci, "ERROR Unknown event condition, HC probably busted\n"); + goto cleanup; + } + /* + * Did we transfer any data, despite the errors that might have + * happened? I.e. did we get past the setup stage? + */ + if (event_trb != ep_ring->dequeue) { + /* The event was for the status stage */ + if (event_trb == td->last_trb) { + td->urb->actual_length = td->urb->transfer_buffer_length; + } else { + /* The event was for the data stage */ + td->urb->actual_length = td->urb->transfer_buffer_length - + TRB_LEN(event->transfer_len); + } + } + while (ep_ring->dequeue != td->last_trb) + inc_deq(xhci, ep_ring, false); + inc_deq(xhci, ep_ring, false); + + /* Clean up the endpoint's TD list */ + urb = td->urb; + list_del(&td->td_list); + kfree(td); + } else { + xhci_dbg(xhci, "FIXME do something for non-control transfers\n"); + } +cleanup: + inc_deq(xhci, xhci->event_ring, true); + set_hc_event_deq(xhci); + + if (urb) { + usb_hcd_unlink_urb_from_ep(xhci_to_hcd(xhci), urb); + spin_unlock(&xhci->lock); + usb_hcd_giveback_urb(xhci_to_hcd(xhci), urb, status); + spin_lock(&xhci->lock); + } + return 0; +} + /* * This function handles all OS-owned events on the event ring. It may drop * xhci->lock between event processing (e.g. to pass up port status changes). @@ -328,6 +521,7 @@ void handle_event(struct xhci_hcd *xhci) { union xhci_trb *event; int update_ptrs = 1; + int ret; if (!xhci->event_ring || !xhci->event_ring->dequeue) { xhci->error_bitmask |= 1 << 1; @@ -351,6 +545,13 @@ void handle_event(struct xhci_hcd *xhci) handle_port_status(xhci, event); update_ptrs = 0; break; + case TRB_TYPE(TRB_TRANSFER): + ret = handle_tx_event(xhci, &event->trans_event); + if (ret < 0) + xhci->error_bitmask |= 1 << 9; + else + update_ptrs = 0; + break; default: xhci->error_bitmask |= 1 << 3; } @@ -364,6 +565,8 @@ void handle_event(struct xhci_hcd *xhci) handle_event(xhci); } +/**** Endpoint Ring Operations ****/ + /* * Generic function for queueing a TRB on a ring. * The caller must have checked to make sure there's room on the ring. @@ -382,6 +585,186 @@ static void queue_trb(struct xhci_hcd *xhci, struct xhci_ring *ring, inc_enq(xhci, ring, consumer); } +/* + * Does various checks on the endpoint ring, and makes it ready to queue num_trbs. + * FIXME allocate segments if the ring is full. + */ +static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, + u32 ep_state, unsigned int num_trbs, gfp_t mem_flags) +{ + /* Make sure the endpoint has been added to xHC schedule */ + xhci_dbg(xhci, "Endpoint state = 0x%x\n", ep_state); + switch (ep_state) { + case EP_STATE_DISABLED: + /* + * USB core changed config/interfaces without notifying us, + * or hardware is reporting the wrong state. + */ + xhci_warn(xhci, "WARN urb submitted to disabled ep\n"); + return -ENOENT; + case EP_STATE_HALTED: + case EP_STATE_ERROR: + xhci_warn(xhci, "WARN waiting for halt or error on ep " + "to be cleared\n"); + /* FIXME event handling code for error needs to clear it */ + /* XXX not sure if this should be -ENOENT or not */ + return -EINVAL; + case EP_STATE_STOPPED: + case EP_STATE_RUNNING: + break; + default: + xhci_err(xhci, "ERROR unknown endpoint state for ep\n"); + /* + * FIXME issue Configure Endpoint command to try to get the HC + * back into a known state. + */ + return -EINVAL; + } + if (!room_on_ring(xhci, ep_ring, num_trbs)) { + /* FIXME allocate more room */ + xhci_err(xhci, "ERROR no room on ep ring\n"); + return -ENOMEM; + } + return 0; +} + +int xhci_prepare_transfer(struct xhci_hcd *xhci, + struct xhci_virt_device *xdev, + unsigned int ep_index, + unsigned int num_trbs, + struct urb *urb, + struct xhci_td **td, + gfp_t mem_flags) +{ + int ret; + + ret = prepare_ring(xhci, xdev->ep_rings[ep_index], + xdev->out_ctx->ep[ep_index].ep_info & EP_STATE_MASK, + num_trbs, mem_flags); + if (ret) + return ret; + *td = kzalloc(sizeof(struct xhci_td), mem_flags); + if (!*td) + return -ENOMEM; + INIT_LIST_HEAD(&(*td)->td_list); + + ret = usb_hcd_link_urb_to_ep(xhci_to_hcd(xhci), urb); + if (unlikely(ret)) { + kfree(*td); + return ret; + } + + (*td)->urb = urb; + urb->hcpriv = (void *) (*td); + /* Add this TD to the tail of the endpoint ring's TD list */ + list_add_tail(&(*td)->td_list, &xdev->ep_rings[ep_index]->td_list); + + return 0; +} + +/* Caller must have locked xhci->lock */ +int queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index) +{ + struct xhci_ring *ep_ring; + int num_trbs; + int ret; + struct usb_ctrlrequest *setup; + struct xhci_generic_trb *start_trb; + int start_cycle; + u32 field; + struct xhci_td *td; + + ep_ring = xhci->devs[slot_id]->ep_rings[ep_index]; + + /* + * Need to copy setup packet into setup TRB, so we can't use the setup + * DMA address. + */ + if (!urb->setup_packet) + return -EINVAL; + + if (!in_interrupt()) + xhci_dbg(xhci, "Queueing ctrl tx for slot id %d, ep %d\n", + slot_id, ep_index); + /* 1 TRB for setup, 1 for status */ + num_trbs = 2; + /* + * Don't need to check if we need additional event data and normal TRBs, + * since data in control transfers will never get bigger than 16MB + * XXX: can we get a buffer that crosses 64KB boundaries? + */ + if (urb->transfer_buffer_length > 0) + num_trbs++; + ret = xhci_prepare_transfer(xhci, xhci->devs[slot_id], ep_index, num_trbs, + urb, &td, mem_flags); + if (ret < 0) + return ret; + + /* + * Don't give the first TRB to the hardware (by toggling the cycle bit) + * until we've finished creating all the other TRBs. The ring's cycle + * state may change as we enqueue the other TRBs, so save it too. + */ + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + + /* Queue setup TRB - see section 6.4.1.2.1 */ + /* FIXME better way to translate setup_packet into two u32 fields? */ + setup = (struct usb_ctrlrequest *) urb->setup_packet; + queue_trb(xhci, ep_ring, false, + /* FIXME endianness is probably going to bite my ass here. */ + setup->bRequestType | setup->bRequest << 8 | setup->wValue << 16, + setup->wIndex | setup->wLength << 16, + TRB_LEN(8) | TRB_INTR_TARGET(0), + /* Immediate data in pointer */ + TRB_IDT | TRB_TYPE(TRB_SETUP)); + + /* If there's data, queue data TRBs */ + field = 0; + if (urb->transfer_buffer_length > 0) { + if (setup->bRequestType & USB_DIR_IN) + field |= TRB_DIR_IN; + queue_trb(xhci, ep_ring, false, + lower_32_bits(urb->transfer_dma), + upper_32_bits(urb->transfer_dma), + TRB_LEN(urb->transfer_buffer_length) | TRB_INTR_TARGET(0), + /* Event on short tx */ + field | TRB_ISP | TRB_TYPE(TRB_DATA) | ep_ring->cycle_state); + } + + /* Save the DMA address of the last TRB in the TD */ + td->last_trb = ep_ring->enqueue; + + /* Queue status TRB - see Table 7 and sections 4.11.2.2 and 6.4.1.2.3 */ + /* If the device sent data, the status stage is an OUT transfer */ + if (urb->transfer_buffer_length > 0 && setup->bRequestType & USB_DIR_IN) + field = 0; + else + field = TRB_DIR_IN; + queue_trb(xhci, ep_ring, false, + 0, + 0, + TRB_INTR_TARGET(0), + /* Event on completion */ + field | TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state); + + /* + * Pass all the TRBs to the hardware at once and make sure this write + * isn't reordered. + */ + wmb(); + start_trb->field[3] |= start_cycle; + field = xhci_readl(xhci, &xhci->dba->doorbell[slot_id]) & DB_MASK; + xhci_writel(xhci, field | EPI_TO_DB(ep_index), &xhci->dba->doorbell[slot_id]); + /* Flush PCI posted writes */ + xhci_readl(xhci, &xhci->dba->doorbell[slot_id]); + + return 0; +} + +/**** Command Ring Operations ****/ + /* Generic function for queueing a command TRB on the command ring */ static int queue_command(struct xhci_hcd *xhci, u32 field1, u32 field2, u32 field3, u32 field4) { diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 4ef6b9e88504..fc8dcd2aa770 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -448,6 +448,9 @@ struct xhci_doorbell_array { #define DB_STREAM_ID_HOST 0x0 #define DB_MASK (0xff << 8) +/* Endpoint Target - bits 0:7 */ +#define EPI_TO_DB(p) (((p) + 1) & 0xff) + /** * struct xhci_slot_ctx @@ -552,13 +555,18 @@ struct xhci_ep_ctx { * 4 - TRB error * 5-7 - reserved */ -#define EP_STATE (0xf) +#define EP_STATE_MASK (0xf) +#define EP_STATE_DISABLED 0 +#define EP_STATE_RUNNING 1 +#define EP_STATE_HALTED 2 +#define EP_STATE_STOPPED 3 +#define EP_STATE_ERROR 4 /* Mult - Max number of burtst within an interval, in EP companion desc. */ #define EP_MULT(p) ((p & 0x3) << 8) /* bits 10:14 are Max Primary Streams */ /* bit 15 is Linear Stream Array */ /* Interval - period between requests to an endpoint - 125u increments. */ -#define EP_INTERVAL (0xff << 16) +#define EP_INTERVAL (0xff << 16) /* ep_info2 bitmasks */ /* @@ -618,7 +626,6 @@ struct xhci_virt_device { dma_addr_t in_ctx_dma; /* FIXME when stream support is added */ struct xhci_ring *ep_rings[31]; - dma_addr_t ep_dma[31]; /* Status of the last command issued for this device */ u32 cmd_status; }; @@ -657,6 +664,9 @@ struct xhci_transfer_event { u32 flags; } __attribute__ ((packed)); +/** Transfer Event bit fields **/ +#define TRB_TO_EP_ID(p) (((p) >> 16) & 0x1f) + /* Completion Code - only applicable for some types of TRBs */ #define COMP_CODE_MASK (0xff << 24) #define GET_COMP_CODE(p) (((p) & COMP_CODE_MASK) >> 24) @@ -877,6 +887,12 @@ union xhci_trb { #define TRBS_PER_SEGMENT 64 #define SEGMENT_SIZE (TRBS_PER_SEGMENT*16) +struct xhci_td { + struct list_head td_list; + struct urb *urb; + union xhci_trb *last_trb; +}; + struct xhci_segment { union xhci_trb *trbs; /* private to HCD */ @@ -892,6 +908,7 @@ struct xhci_ring { union xhci_trb *dequeue; struct xhci_segment *deq_seg; unsigned int deq_updates; + struct list_head td_list; /* * Write the cycle state into the TRB cycle field to give ownership of * the TRB to the host controller (if we are the producer), or to check @@ -1042,6 +1059,8 @@ void xhci_print_ir_set(struct xhci_hcd *xhci, struct intr_reg *ir_set, int set_n void xhci_print_registers(struct xhci_hcd *xhci); void xhci_dbg_regs(struct xhci_hcd *xhci); void xhci_print_run_regs(struct xhci_hcd *xhci); +void xhci_print_trb_offsets(struct xhci_hcd *xhci, union xhci_trb *trb); +void xhci_debug_trb(struct xhci_hcd *xhci, union xhci_trb *trb); void xhci_debug_segment(struct xhci_hcd *xhci, struct xhci_segment *seg); void xhci_debug_ring(struct xhci_hcd *xhci, struct xhci_ring *ring); void xhci_dbg_erst(struct xhci_hcd *xhci, struct xhci_erst *erst); @@ -1055,6 +1074,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags); void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id); int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, struct usb_device *udev, gfp_t flags); int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *udev); +unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc); #ifdef CONFIG_PCI /* xHCI PCI glue */ @@ -1074,6 +1094,8 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd); int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev); void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev); int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev); +int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags); +int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); /* xHCI ring, segment, TRB, and TD functions */ dma_addr_t trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); @@ -1083,6 +1105,7 @@ void handle_event(struct xhci_hcd *xhci); void set_hc_event_deq(struct xhci_hcd *xhci); int queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id); int queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); +int queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); /* xHCI roothub code */ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, -- cgit v1.2.3 From 663c30d0829d556efabd5fbd98fb8473da7fe694 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:58:14 -0700 Subject: USB: Parse and store the SuperSpeed endpoint companion descriptors. The USB 3.0 bus specification added an "Endpoint Companion" descriptor that is supposed to follow all SuperSpeed Endpoint descriptors. This descriptor is used to extend the bus protocol to allow more packets to be sent to an endpoint per "microframe". The word microframe was removed from the USB 3.0 specification because the host controller does not send Start Of Frame (SOF) symbols down the USB 3.0 wires. The descriptor defines a bMaxBurst field, which indicates the number of packets of wMaxPacketSize that a SuperSpeed device can send or recieve in a service interval. All non-control endpoints may set this value as high as 16 packets (bMaxBurst = 15). The descriptor also allows isochronous endpoints to further specify that they can send and receive multiple bursts per service interval. The bmAttributes allows them to specify a "Mult" of up to 3 (bmAttributes = 2). Bulk endpoints use bmAttributes to report the number of "Streams" they support. This was an extension of the endpoint pipe concept to allow multiple mass storage device commands to be outstanding for one bulk endpoint at a time. This should allow USB 3.0 mass storage devices to support SCSI command queueing. Bulk endpoints can say they support up to 2^16 (65,536) streams. The information in the endpoint companion descriptor must be stored with the other device, config, interface, and endpoint descriptors because the host controller needs to access them quickly, and we need to install some default values if a SuperSpeed device doesn't provide an endpoint companion descriptor. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/config.c | 189 +++++++++++++++++++++++++++++++++++++++++++--- include/linux/usb.h | 16 ++++ include/linux/usb/ch9.h | 16 ++++ 3 files changed, 212 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index e9426acf5682..7103758bb486 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -19,6 +19,32 @@ static inline const char *plural(int n) return (n == 1 ? "" : "s"); } +/* FIXME: this is a kludge */ +static int find_next_descriptor_more(unsigned char *buffer, int size, + int dt1, int dt2, int dt3, int *num_skipped) +{ + struct usb_descriptor_header *h; + int n = 0; + unsigned char *buffer0 = buffer; + + /* Find the next descriptor of type dt1 or dt2 or dt3 */ + while (size > 0) { + h = (struct usb_descriptor_header *) buffer; + if (h->bDescriptorType == dt1 || h->bDescriptorType == dt2 || + h->bDescriptorType == dt3) + break; + buffer += h->bLength; + size -= h->bLength; + ++n; + } + + /* Store the number of descriptors skipped and return the + * number of bytes skipped */ + if (num_skipped) + *num_skipped = n; + return buffer - buffer0; +} + static int find_next_descriptor(unsigned char *buffer, int size, int dt1, int dt2, int *num_skipped) { @@ -43,6 +69,128 @@ static int find_next_descriptor(unsigned char *buffer, int size, return buffer - buffer0; } +static int usb_parse_endpoint_companion(struct device *ddev, int cfgno, + int inum, int asnum, struct usb_host_endpoint *ep, + int num_ep, unsigned char *buffer, int size) +{ + unsigned char *buffer_start = buffer; + struct usb_ep_comp_descriptor *desc; + int retval; + int num_skipped; + int max_tx; + int i; + + /* Allocate space for the companion descriptor */ + ep->ep_comp = kzalloc(sizeof(struct usb_host_ep_comp), GFP_KERNEL); + if (!ep->ep_comp) + return -ENOMEM; + desc = (struct usb_ep_comp_descriptor *) buffer; + if (desc->bDescriptorType != USB_DT_SS_ENDPOINT_COMP) { + dev_warn(ddev, "No SuperSpeed endpoint companion for config %d " + " interface %d altsetting %d ep %d: " + "using minimum values\n", + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ep_comp->desc.bLength = USB_DT_EP_COMP_SIZE; + ep->ep_comp->desc.bDescriptorType = USB_DT_SS_ENDPOINT_COMP; + ep->ep_comp->desc.bMaxBurst = 0; + /* + * Leave bmAttributes as zero, which will mean no streams for + * bulk, and isoc won't support multiple bursts of packets. + * With bursts of only one packet, and a Mult of 1, the max + * amount of data moved per endpoint service interval is one + * packet. + */ + if (usb_endpoint_xfer_isoc(&ep->desc) || + usb_endpoint_xfer_int(&ep->desc)) + ep->ep_comp->desc.wBytesPerInterval = + ep->desc.wMaxPacketSize; + /* + * The next descriptor is for an Endpoint or Interface, + * no extra descriptors to copy into the companion structure, + * and we didn't eat up any of the buffer. + */ + retval = 0; + goto valid; + } + memcpy(&ep->ep_comp->desc, desc, USB_DT_EP_COMP_SIZE); + desc = &ep->ep_comp->desc; + buffer += desc->bLength; + size -= desc->bLength; + + /* Eat up the other descriptors we don't care about */ + ep->ep_comp->extra = buffer; + i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT, + USB_DT_INTERFACE, &num_skipped); + ep->ep_comp->extralen = i; + buffer += i; + size -= i; + retval = buffer - buffer_start + i; + if (num_skipped > 0) + dev_dbg(ddev, "skipped %d descriptor%s after %s\n", + num_skipped, plural(num_skipped), + "SuperSpeed endpoint companion"); + + /* Check the various values */ + if (usb_endpoint_xfer_control(&ep->desc) && desc->bMaxBurst != 0) { + dev_warn(ddev, "Control endpoint with bMaxBurst = %d in " + "config %d interface %d altsetting %d ep %d: " + "setting to zero\n", desc->bMaxBurst, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + desc->bMaxBurst = 0; + } + if (desc->bMaxBurst > 15) { + dev_warn(ddev, "Endpoint with bMaxBurst = %d in " + "config %d interface %d altsetting %d ep %d: " + "setting to 15\n", desc->bMaxBurst, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + desc->bMaxBurst = 15; + } + if ((usb_endpoint_xfer_control(&ep->desc) || usb_endpoint_xfer_int(&ep->desc)) + && desc->bmAttributes != 0) { + dev_warn(ddev, "%s endpoint with bmAttributes = %d in " + "config %d interface %d altsetting %d ep %d: " + "setting to zero\n", + usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk", + desc->bmAttributes, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + desc->bmAttributes = 0; + } + if (usb_endpoint_xfer_bulk(&ep->desc) && desc->bmAttributes > 16) { + dev_warn(ddev, "Bulk endpoint with more than 65536 streams in " + "config %d interface %d altsetting %d ep %d: " + "setting to max\n", + cfgno, inum, asnum, ep->desc.bEndpointAddress); + desc->bmAttributes = 16; + } + if (usb_endpoint_xfer_isoc(&ep->desc) && desc->bmAttributes > 2) { + dev_warn(ddev, "Isoc endpoint has Mult of %d in " + "config %d interface %d altsetting %d ep %d: " + "setting to 3\n", desc->bmAttributes + 1, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + desc->bmAttributes = 2; + } + if (usb_endpoint_xfer_isoc(&ep->desc)) { + max_tx = ep->desc.wMaxPacketSize * (desc->bMaxBurst + 1) * + (desc->bmAttributes + 1); + } else if (usb_endpoint_xfer_int(&ep->desc)) { + max_tx = ep->desc.wMaxPacketSize * (desc->bMaxBurst + 1); + } else { + goto valid; + } + if (desc->wBytesPerInterval > max_tx) { + dev_warn(ddev, "%s endpoint with wBytesPerInterval of %d in " + "config %d interface %d altsetting %d ep %d: " + "setting to %d\n", + usb_endpoint_xfer_isoc(&ep->desc) ? "Isoc" : "Int", + desc->wBytesPerInterval, + cfgno, inum, asnum, ep->desc.bEndpointAddress, + max_tx); + desc->wBytesPerInterval = max_tx; + } +valid: + return retval; +} + static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum, int asnum, struct usb_host_interface *ifp, int num_ep, unsigned char *buffer, int size) @@ -50,7 +198,7 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum, unsigned char *buffer0 = buffer; struct usb_endpoint_descriptor *d; struct usb_host_endpoint *endpoint; - int n, i, j; + int n, i, j, retval; d = (struct usb_endpoint_descriptor *) buffer; buffer += d->bLength; @@ -162,17 +310,38 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum, cfgno, inum, asnum, d->bEndpointAddress, maxp); } - - /* Skip over any Class Specific or Vendor Specific descriptors; - * find the next endpoint or interface descriptor */ - endpoint->extra = buffer; - i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT, - USB_DT_INTERFACE, &n); - endpoint->extralen = i; + /* Allocate room for and parse any endpoint companion descriptors */ + if (to_usb_device(ddev)->speed == USB_SPEED_SUPER) { + endpoint->extra = buffer; + i = find_next_descriptor_more(buffer, size, USB_DT_SS_ENDPOINT_COMP, + USB_DT_ENDPOINT, USB_DT_INTERFACE, &n); + endpoint->extralen = i; + buffer += i; + size -= i; + + if (size > 0) { + retval = usb_parse_endpoint_companion(ddev, cfgno, inum, asnum, + endpoint, num_ep, buffer, size); + if (retval >= 0) { + buffer += retval; + retval = buffer - buffer0; + } + } else { + retval = buffer - buffer0; + } + } else { + /* Skip over any Class Specific or Vendor Specific descriptors; + * find the next endpoint or interface descriptor */ + endpoint->extra = buffer; + i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT, + USB_DT_INTERFACE, &n); + endpoint->extralen = i; + retval = buffer - buffer0 + i; + } if (n > 0) dev_dbg(ddev, "skipped %d descriptor%s after %s\n", n, plural(n), "endpoint"); - return buffer - buffer0 + i; + return retval; skip_to_next_endpoint_or_interface_descriptor: i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT, @@ -453,6 +622,8 @@ static int usb_parse_configuration(struct device *ddev, int cfgidx, kref_init(&intfc->ref); } + /* FIXME: parse the BOS descriptor */ + /* Skip over any Class Specific or Vendor Specific descriptors; * find the first interface descriptor */ config->extra = buffer; diff --git a/include/linux/usb.h b/include/linux/usb.h index 112a2d6e922f..13bced521b83 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -36,6 +36,7 @@ struct wusb_dev; * - configs have one (often) or more interfaces; * - interfaces have one (usually) or more settings; * - each interface setting has zero or (usually) more endpoints. + * - a SuperSpeed endpoint has a companion descriptor * * And there might be other descriptors mixed in with those. * @@ -44,6 +45,19 @@ struct wusb_dev; struct ep_device; +/* For SS devices */ +/** + * struct usb_host_ep_comp - Valid for SuperSpeed devices only + * @desc: endpoint companion descriptor, wMaxPacketSize in native byteorder + * @extra: descriptors following this endpoint companion descriptor + * @extralen: how many bytes of "extra" are valid + */ +struct usb_host_ep_comp { + struct usb_ep_comp_descriptor desc; + unsigned char *extra; /* Extra descriptors */ + int extralen; +}; + /** * struct usb_host_endpoint - host-side endpoint descriptor and queue * @desc: descriptor for this endpoint, wMaxPacketSize in native byteorder @@ -51,6 +65,7 @@ struct ep_device; * @hcpriv: for use by HCD; typically holds hardware dma queue head (QH) * with one or more transfer descriptors (TDs) per urb * @ep_dev: ep_device for sysfs info + * @ep_comp: companion descriptor information for this endpoint * @extra: descriptors following this endpoint in the configuration * @extralen: how many bytes of "extra" are valid * @enabled: URBs may be submitted to this endpoint @@ -63,6 +78,7 @@ struct usb_host_endpoint { struct list_head urb_list; void *hcpriv; struct ep_device *ep_dev; /* For sysfs info */ + struct usb_host_ep_comp *ep_comp; /* For SS devices */ unsigned char *extra; /* Extra descriptors */ int extralen; diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h index 93bfe6352342..9e9c5c0a3d72 100644 --- a/include/linux/usb/ch9.h +++ b/include/linux/usb/ch9.h @@ -191,6 +191,8 @@ struct usb_ctrlrequest { #define USB_DT_WIRE_ADAPTER 0x21 #define USB_DT_RPIPE 0x22 #define USB_DT_CS_RADIO_CONTROL 0x23 +/* From the USB 3.0 spec */ +#define USB_DT_SS_ENDPOINT_COMP 0x30 /* Conventional codes for class-specific descriptors. The convention is * defined in the USB "Common Class" Spec (3.11). Individual class specs @@ -535,6 +537,20 @@ static inline int usb_endpoint_is_isoc_out( /*-------------------------------------------------------------------------*/ +/* USB_DT_SS_ENDPOINT_COMP: SuperSpeed Endpoint Companion descriptor */ +struct usb_ep_comp_descriptor { + __u8 bLength; + __u8 bDescriptorType; + + __u8 bMaxBurst; + __u8 bmAttributes; + __u16 wBytesPerInterval; +} __attribute__ ((packed)); + +#define USB_DT_EP_COMP_SIZE 6 + +/*-------------------------------------------------------------------------*/ + /* USB_DT_DEVICE_QUALIFIER: Device Qualifier descriptor */ struct usb_qualifier_descriptor { __u8 bLength; -- cgit v1.2.3 From 79abb1ab13cee5ba488210798b6e7bbae0b391ac Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:58:26 -0700 Subject: USB: Support for bandwidth allocation. Originally, the USB core had no support for allocating bandwidth when a particular configuration or alternate setting for an interface was selected. Instead, the device driver's URB submission would fail if there was not enough bandwidth for a periodic endpoint. Drivers could work around this, by using the scatter-gather list API to guarantee bandwidth. This patch adds host controller API to allow the USB core to allocate or deallocate bandwidth for an endpoint. Endpoints are added to or dropped from a copy of the current schedule by calling add_endpoint() or drop_endpoint(), and then the schedule is atomically evaluated with a call to check_bandwidth(). This allows all the endpoints for a new configuration or alternate setting to be added at the same time that the endpoints from the old configuration or alt setting are dropped. Endpoints must be added to the schedule before any URBs are submitted to them. The HCD must be allowed to reject a new configuration or alt setting before the control transfer is sent to the device requesting the change. It may reject the change because there is not enough bandwidth, not enough internal resources (such as memory on an embedded host controller), or perhaps even for security reasons in a virtualized environment. If the call to check_bandwidth() fails, the USB core must call reset_bandwidth(). This causes the schedule to be reverted back to the state it was in just after the last successful check_bandwidth() call. If the call succeeds, the host controller driver (and hardware) will have changed its internal state to match the new configuration or alternate setting. The USB core can then issue a control transfer to the device to change the configuration or alt setting. This allows the core to test new configurations or alternate settings before unbinding drivers bound to interfaces in the old configuration. WIP: The USB core must add endpoints from all interfaces in a configuration to the schedule, because a driver may claim that interface at any time. A slight optimization might be to add the endpoints to the schedule once a driver claims that interface. FIXME This patch does not cover changing alternate settings, but it does handle a configuration change or de-configuration. FIXME The code for managing the schedule is currently HCD specific. A generic scheduling algorithm could be added for host controllers without built-in scheduling support. For now, if a host controller does not define the check_bandwidth() function, the call to usb_hcd_check_bandwidth() will always succeed. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hcd.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/usb/core/hcd.h | 32 +++++++++++++++++ drivers/usb/core/message.c | 20 +++++++++++ drivers/usb/core/urb.c | 6 ++++ 4 files changed, 144 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 823744d80cb9..b2da4753b12e 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1560,6 +1560,92 @@ rescan: } } +/* Check whether a new configuration or alt setting for an interface + * will exceed the bandwidth for the bus (or the host controller resources). + * Only pass in a non-NULL config or interface, not both! + * Passing NULL for both new_config and new_intf means the device will be + * de-configured by issuing a set configuration 0 command. + */ +int usb_hcd_check_bandwidth(struct usb_device *udev, + struct usb_host_config *new_config, + struct usb_interface *new_intf) +{ + int num_intfs, i, j; + struct usb_interface_cache *intf_cache; + struct usb_host_interface *alt = 0; + int ret = 0; + struct usb_hcd *hcd; + struct usb_host_endpoint *ep; + + hcd = bus_to_hcd(udev->bus); + if (!hcd->driver->check_bandwidth) + return 0; + + /* Configuration is being removed - set configuration 0 */ + if (!new_config && !new_intf) { + for (i = 1; i < 16; ++i) { + ep = udev->ep_out[i]; + if (ep) + hcd->driver->drop_endpoint(hcd, udev, ep); + ep = udev->ep_in[i]; + if (ep) + hcd->driver->drop_endpoint(hcd, udev, ep); + } + hcd->driver->check_bandwidth(hcd, udev); + return 0; + } + /* Check if the HCD says there's enough bandwidth. Enable all endpoints + * each interface's alt setting 0 and ask the HCD to check the bandwidth + * of the bus. There will always be bandwidth for endpoint 0, so it's + * ok to exclude it. + */ + if (new_config) { + num_intfs = new_config->desc.bNumInterfaces; + /* Remove endpoints (except endpoint 0, which is always on the + * schedule) from the old config from the schedule + */ + for (i = 1; i < 16; ++i) { + ep = udev->ep_out[i]; + if (ep) { + ret = hcd->driver->drop_endpoint(hcd, udev, ep); + if (ret < 0) + goto reset; + } + ep = udev->ep_in[i]; + if (ep) { + ret = hcd->driver->drop_endpoint(hcd, udev, ep); + if (ret < 0) + goto reset; + } + } + for (i = 0; i < num_intfs; ++i) { + + /* Dig the endpoints for alt setting 0 out of the + * interface cache for this interface + */ + intf_cache = new_config->intf_cache[i]; + for (j = 0; j < intf_cache->num_altsetting; j++) { + if (intf_cache->altsetting[j].desc.bAlternateSetting == 0) + alt = &intf_cache->altsetting[j]; + } + if (!alt) { + printk(KERN_DEBUG "Did not find alt setting 0 for intf %d\n", i); + continue; + } + for (j = 0; j < alt->desc.bNumEndpoints; j++) { + ret = hcd->driver->add_endpoint(hcd, udev, &alt->endpoint[j]); + if (ret < 0) + goto reset; + } + } + } + ret = hcd->driver->check_bandwidth(hcd, udev); +reset: + if (ret < 0) + hcd->driver->reset_bandwidth(hcd, udev); + return ret; +} + /* Disables the endpoint: synchronizes with the hcd to make sure all * endpoint state is gone from hardware. usb_hcd_flush_endpoint() must * have been called previously. Use for set_configuration, set_interface, diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index ae6d9db41ca9..d397ecfd5b17 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -232,6 +232,35 @@ struct hc_driver { int (*alloc_dev)(struct usb_hcd *, struct usb_device *); /* Called by usb_release_dev to free HC device structures */ void (*free_dev)(struct usb_hcd *, struct usb_device *); + + /* Bandwidth computation functions */ + /* Note that add_endpoint() can only be called once per endpoint before + * check_bandwidth() or reset_bandwidth() must be called. + * drop_endpoint() can only be called once per endpoint also. + * A call to xhci_drop_endpoint() followed by a call to xhci_add_endpoint() will + * add the endpoint to the schedule with possibly new parameters denoted by a + * different endpoint descriptor in usb_host_endpoint. + * A call to xhci_add_endpoint() followed by a call to xhci_drop_endpoint() is + * not allowed. + */ + /* Allocate endpoint resources and add them to a new schedule */ + int (*add_endpoint)(struct usb_hcd *, struct usb_device *, struct usb_host_endpoint *); + /* Drop an endpoint from a new schedule */ + int (*drop_endpoint)(struct usb_hcd *, struct usb_device *, struct usb_host_endpoint *); + /* Check that a new hardware configuration, set using + * endpoint_enable and endpoint_disable, does not exceed bus + * bandwidth. This must be called before any set configuration + * or set interface requests are sent to the device. + */ + int (*check_bandwidth)(struct usb_hcd *, struct usb_device *); + /* Reset the device schedule to the last known good schedule, + * which was set from a previous successful call to + * check_bandwidth(). This reverts any add_endpoint() and + * drop_endpoint() calls since that last successful call. + * Used for when a check_bandwidth() call fails due to resource + * or bandwidth constraints. + */ + void (*reset_bandwidth)(struct usb_hcd *, struct usb_device *); /* Returns the hardware-chosen device address */ int (*address_device)(struct usb_hcd *, struct usb_device *udev); }; @@ -252,6 +281,9 @@ extern void usb_hcd_disable_endpoint(struct usb_device *udev, extern void usb_hcd_reset_endpoint(struct usb_device *udev, struct usb_host_endpoint *ep); extern void usb_hcd_synchronize_unlinks(struct usb_device *udev); +extern int usb_hcd_check_bandwidth(struct usb_device *udev, + struct usb_host_config *new_config, + struct usb_interface *new_intf); extern int usb_hcd_get_frame_number(struct usb_device *udev); extern struct usb_hcd *usb_create_hcd(const struct hc_driver *driver, diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 9bd26dec7599..3a2e69ec2f29 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -510,6 +510,10 @@ EXPORT_SYMBOL_GPL(usb_sg_init); * could be transferred. That capability is less useful for low or full * speed interrupt endpoints, which allow at most one packet per millisecond, * of at most 8 or 64 bytes (respectively). + * + * It is not necessary to call this function to reserve bandwidth for devices + * under an xHCI host controller, as the bandwidth is reserved when the + * configuration or interface alt setting is selected. */ void usb_sg_wait(struct usb_sg_request *io) { @@ -1653,6 +1657,21 @@ free_interfaces: if (ret) goto free_interfaces; + /* Make sure we have bandwidth (and available HCD resources) for this + * configuration. Remove endpoints from the schedule if we're dropping + * this configuration to set configuration 0. After this point, the + * host controller will not allow submissions to dropped endpoints. If + * this call fails, the device state is unchanged. + */ + if (cp) + ret = usb_hcd_check_bandwidth(dev, cp, NULL); + else + ret = usb_hcd_check_bandwidth(dev, NULL, NULL); + if (ret < 0) { + usb_autosuspend_device(dev); + goto free_interfaces; + } + /* if it's already configured, clear out old state first. * getting rid of old interfaces means unbinding their drivers. */ @@ -1675,6 +1694,7 @@ free_interfaces: dev->actconfig = cp; if (!cp) { usb_set_device_state(dev, USB_STATE_ADDRESS); + usb_hcd_check_bandwidth(dev, NULL, NULL); usb_autosuspend_device(dev); goto free_interfaces; } diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 02eb0ef7a4c3..0885d4abdc62 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -241,6 +241,12 @@ EXPORT_SYMBOL_GPL(usb_unanchor_urb); * If the USB subsystem can't allocate sufficient bandwidth to perform * the periodic request, submitting such a periodic request should fail. * + * For devices under xHCI, the bandwidth is reserved at configuration time, or + * when the alt setting is selected. If there is not enough bus bandwidth, the + * configuration/alt setting request will fail. Therefore, submissions to + * periodic endpoints on devices under xHCI should never fail due to bandwidth + * constraints. + * * Device drivers must explicitly request that repetition, by ensuring that * some URB is always on the endpoint's queue (except possibly for short * periods during completion callacks). When there is no longer an urb -- cgit v1.2.3 From f94e0186312b0fc39f41eed4e21836ed74b7efe1 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:58:38 -0700 Subject: USB: xhci: Bandwidth allocation support Since the xHCI host controller hardware (xHC) has an internal schedule, it needs a better representation of what devices are consuming bandwidth on the bus. Each device is represented by a device context, with data about the device, endpoints, and pointers to each endpoint ring. We need to update the endpoint information for a device context before a new configuration or alternate interface setting is selected. We setup an input device context with modified endpoint information and newly allocated endpoint rings, and then submit a Configure Endpoint Command to the hardware. The host controller can reject the new configuration if it exceeds the bus bandwidth, or the host controller doesn't have enough internal resources for the configuration. If the command fails, we still have the older device context with the previous configuration. If the command succeeds, we free the old endpoint rings. The root hub isn't a real device, so always say yes to any bandwidth changes for it. The USB core will enable, disable, and then enable endpoint 0 several times during the initialization sequence. The device will always have an endpoint ring for endpoint 0 and bandwidth allocated for that, unless the device is disconnected or gets a SetAddress 0 request. So we don't pay attention for when xhci_check_bandwidth() is called for a re-add of endpoint 0. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-hcd.c | 370 ++++++++++++++++++++++++++++++++++++++++++- drivers/usb/host/xhci-mem.c | 174 +++++++++++++++++++- drivers/usb/host/xhci-pci.c | 4 + drivers/usb/host/xhci-ring.c | 11 ++ drivers/usb/host/xhci.h | 18 ++- 5 files changed, 572 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 5d94b4ffac92..50ab525f65be 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -530,6 +530,26 @@ unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc) return index; } +/* Find the flag for this endpoint (for use in the control context). Use the + * endpoint index to create a bitmask. The slot context is bit 0, endpoint 0 is + * bit 1, etc. + */ +unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc) +{ + return 1 << (xhci_get_endpoint_index(desc) + 1); +} + +/* Compute the last valid endpoint context index. Basically, this is the + * endpoint index plus one. For slot contexts with more than valid endpoint, + * we find the most significant bit set in the added contexts flags. + * e.g. ep 1 IN (with epnum 0x81) => added_ctxs = 0b1000 + * fls(0b1000) = 4, but the endpoint context index is 3, so subtract one. + */ +static inline unsigned int xhci_last_valid_endpoint(u32 added_ctxs) +{ + return fls(added_ctxs) - 1; +} + /* Returns 1 if the arguments are OK; * returns 0 this is a root hub; returns -EINVAL for NULL pointers. */ @@ -602,6 +622,349 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) return -ENOSYS; } +/* Drop an endpoint from a new bandwidth configuration for this device. + * Only one call to this function is allowed per endpoint before + * check_bandwidth() or reset_bandwidth() must be called. + * A call to xhci_drop_endpoint() followed by a call to xhci_add_endpoint() will + * add the endpoint to the schedule with possibly new parameters denoted by a + * different endpoint descriptor in usb_host_endpoint. + * A call to xhci_add_endpoint() followed by a call to xhci_drop_endpoint() is + * not allowed. + */ +int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + unsigned long flags; + struct xhci_hcd *xhci; + struct xhci_device_control *in_ctx; + unsigned int last_ctx; + unsigned int ep_index; + struct xhci_ep_ctx *ep_ctx; + u32 drop_flag; + u32 new_add_flags, new_drop_flags, new_slot_info; + int ret; + + ret = xhci_check_args(hcd, udev, ep, 1, __func__); + xhci_dbg(xhci, "%s called for udev %#x\n", __func__, (unsigned int) udev); + if (ret <= 0) + return ret; + xhci = hcd_to_xhci(hcd); + + drop_flag = xhci_get_endpoint_flag(&ep->desc); + if (drop_flag == SLOT_FLAG || drop_flag == EP0_FLAG) { + xhci_dbg(xhci, "xHCI %s - can't drop slot or ep 0 %#x\n", + __func__, drop_flag); + return 0; + } + + spin_lock_irqsave(&xhci->lock, flags); + if (!xhci->devs || !xhci->devs[udev->slot_id]) { + xhci_warn(xhci, "xHCI %s called with unaddressed device\n", + __func__); + spin_unlock_irqrestore(&xhci->lock, flags); + return -EINVAL; + } + + in_ctx = xhci->devs[udev->slot_id]->in_ctx; + ep_index = xhci_get_endpoint_index(&ep->desc); + ep_ctx = &xhci->devs[udev->slot_id]->out_ctx->ep[ep_index]; + /* If the HC already knows the endpoint is disabled, + * or the HCD has noted it is disabled, ignore this request + */ + if ((ep_ctx->ep_info & EP_STATE_MASK) == EP_STATE_DISABLED || + in_ctx->drop_flags & xhci_get_endpoint_flag(&ep->desc)) { + xhci_warn(xhci, "xHCI %s called with disabled ep %#x\n", + __func__, (unsigned int) ep); + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; + } + + in_ctx->drop_flags |= drop_flag; + new_drop_flags = in_ctx->drop_flags; + + in_ctx->add_flags = ~drop_flag; + new_add_flags = in_ctx->add_flags; + + last_ctx = xhci_last_valid_endpoint(in_ctx->add_flags); + /* Update the last valid endpoint context, if we deleted the last one */ + if ((in_ctx->slot.dev_info & LAST_CTX_MASK) > LAST_CTX(last_ctx)) { + in_ctx->slot.dev_info &= ~LAST_CTX_MASK; + in_ctx->slot.dev_info |= LAST_CTX(last_ctx); + } + new_slot_info = in_ctx->slot.dev_info; + + xhci_endpoint_zero(xhci, xhci->devs[udev->slot_id], ep); + + spin_unlock_irqrestore(&xhci->lock, flags); + + xhci_dbg(xhci, "drop ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x, new slot info = %#x\n", + (unsigned int) ep->desc.bEndpointAddress, + udev->slot_id, + (unsigned int) new_drop_flags, + (unsigned int) new_add_flags, + (unsigned int) new_slot_info); + return 0; +} + +/* Add an endpoint to a new possible bandwidth configuration for this device. + * Only one call to this function is allowed per endpoint before + * check_bandwidth() or reset_bandwidth() must be called. + * A call to xhci_drop_endpoint() followed by a call to xhci_add_endpoint() will + * add the endpoint to the schedule with possibly new parameters denoted by a + * different endpoint descriptor in usb_host_endpoint. + * A call to xhci_add_endpoint() followed by a call to xhci_drop_endpoint() is + * not allowed. + */ +int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + unsigned long flags; + struct xhci_hcd *xhci; + struct xhci_device_control *in_ctx; + unsigned int ep_index; + struct xhci_ep_ctx *ep_ctx; + u32 added_ctxs; + unsigned int last_ctx; + u32 new_add_flags, new_drop_flags, new_slot_info; + int ret = 0; + + ret = xhci_check_args(hcd, udev, ep, 1, __func__); + if (ret <= 0) + return ret; + xhci = hcd_to_xhci(hcd); + + added_ctxs = xhci_get_endpoint_flag(&ep->desc); + last_ctx = xhci_last_valid_endpoint(added_ctxs); + if (added_ctxs == SLOT_FLAG || added_ctxs == EP0_FLAG) { + /* FIXME when we have to issue an evaluate endpoint command to + * deal with ep0 max packet size changing once we get the + * descriptors + */ + xhci_dbg(xhci, "xHCI %s - can't add slot or ep 0 %#x\n", + __func__, added_ctxs); + return 0; + } + + spin_lock_irqsave(&xhci->lock, flags); + if (!xhci->devs || !xhci->devs[udev->slot_id]) { + xhci_warn(xhci, "xHCI %s called with unaddressed device\n", + __func__); + spin_unlock_irqrestore(&xhci->lock, flags); + return -EINVAL; + } + + in_ctx = xhci->devs[udev->slot_id]->in_ctx; + ep_index = xhci_get_endpoint_index(&ep->desc); + ep_ctx = &xhci->devs[udev->slot_id]->out_ctx->ep[ep_index]; + /* If the HCD has already noted the endpoint is enabled, + * ignore this request. + */ + if (in_ctx->add_flags & xhci_get_endpoint_flag(&ep->desc)) { + xhci_warn(xhci, "xHCI %s called with enabled ep %#x\n", + __func__, (unsigned int) ep); + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; + } + + if (xhci_endpoint_init(xhci, xhci->devs[udev->slot_id], udev, ep) < 0) { + dev_dbg(&udev->dev, "%s - could not initialize ep %#x\n", + __func__, ep->desc.bEndpointAddress); + spin_unlock_irqrestore(&xhci->lock, flags); + return -ENOMEM; + } + + in_ctx->add_flags |= added_ctxs; + new_add_flags = in_ctx->add_flags; + + /* If xhci_endpoint_disable() was called for this endpoint, but the + * xHC hasn't been notified yet through the check_bandwidth() call, + * this re-adds a new state for the endpoint from the new endpoint + * descriptors. We must drop and re-add this endpoint, so we leave the + * drop flags alone. + */ + new_drop_flags = in_ctx->drop_flags; + + /* Update the last valid endpoint context, if we just added one past */ + if ((in_ctx->slot.dev_info & LAST_CTX_MASK) < LAST_CTX(last_ctx)) { + in_ctx->slot.dev_info &= ~LAST_CTX_MASK; + in_ctx->slot.dev_info |= LAST_CTX(last_ctx); + } + new_slot_info = in_ctx->slot.dev_info; + spin_unlock_irqrestore(&xhci->lock, flags); + + xhci_dbg(xhci, "add ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x, new slot info = %#x\n", + (unsigned int) ep->desc.bEndpointAddress, + udev->slot_id, + (unsigned int) new_drop_flags, + (unsigned int) new_add_flags, + (unsigned int) new_slot_info); + return 0; +} + +static void xhci_zero_in_ctx(struct xhci_virt_device *virt_dev) +{ + struct xhci_ep_ctx *ep_ctx; + int i; + + /* When a device's add flag and drop flag are zero, any subsequent + * configure endpoint command will leave that endpoint's state + * untouched. Make sure we don't leave any old state in the input + * endpoint contexts. + */ + virt_dev->in_ctx->drop_flags = 0; + virt_dev->in_ctx->add_flags = 0; + virt_dev->in_ctx->slot.dev_info &= ~LAST_CTX_MASK; + /* Endpoint 0 is always valid */ + virt_dev->in_ctx->slot.dev_info |= LAST_CTX(1); + for (i = 1; i < 31; ++i) { + ep_ctx = &virt_dev->in_ctx->ep[i]; + ep_ctx->ep_info = 0; + ep_ctx->ep_info2 = 0; + ep_ctx->deq[0] = 0; + ep_ctx->deq[1] = 0; + ep_ctx->tx_info = 0; + } +} + +int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) +{ + int i; + int ret = 0; + int timeleft; + unsigned long flags; + struct xhci_hcd *xhci; + struct xhci_virt_device *virt_dev; + + ret = xhci_check_args(hcd, udev, NULL, 0, __func__); + if (ret <= 0) + return ret; + xhci = hcd_to_xhci(hcd); + + spin_lock_irqsave(&xhci->lock, flags); + if (!udev->slot_id || !xhci->devs || !xhci->devs[udev->slot_id]) { + xhci_warn(xhci, "xHCI %s called with unaddressed device\n", + __func__); + spin_unlock_irqrestore(&xhci->lock, flags); + return -EINVAL; + } + xhci_dbg(xhci, "%s called for udev %#x\n", __func__, (unsigned int) udev); + virt_dev = xhci->devs[udev->slot_id]; + + /* See section 4.6.6 - A0 = 1; A1 = D0 = D1 = 0 */ + virt_dev->in_ctx->add_flags |= SLOT_FLAG; + virt_dev->in_ctx->add_flags &= ~EP0_FLAG; + virt_dev->in_ctx->drop_flags &= ~SLOT_FLAG; + virt_dev->in_ctx->drop_flags &= ~EP0_FLAG; + xhci_dbg(xhci, "New Input Control Context:\n"); + xhci_dbg_ctx(xhci, virt_dev->in_ctx, virt_dev->in_ctx_dma, + LAST_CTX_TO_EP_NUM(virt_dev->in_ctx->slot.dev_info)); + + ret = queue_configure_endpoint(xhci, virt_dev->in_ctx_dma, udev->slot_id); + if (ret < 0) { + xhci_dbg(xhci, "FIXME allocate a new ring segment\n"); + spin_unlock_irqrestore(&xhci->lock, flags); + return -ENOMEM; + } + ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* Wait for the configure endpoint command to complete */ + timeleft = wait_for_completion_interruptible_timeout( + &virt_dev->cmd_completion, + USB_CTRL_SET_TIMEOUT); + if (timeleft <= 0) { + xhci_warn(xhci, "%s while waiting for configure endpoint command\n", + timeleft == 0 ? "Timeout" : "Signal"); + /* FIXME cancel the configure endpoint command */ + return -ETIME; + } + + spin_lock_irqsave(&xhci->lock, flags); + switch (virt_dev->cmd_status) { + case COMP_ENOMEM: + dev_warn(&udev->dev, "Not enough host controller resources " + "for new device state.\n"); + ret = -ENOMEM; + /* FIXME: can we allocate more resources for the HC? */ + break; + case COMP_BW_ERR: + dev_warn(&udev->dev, "Not enough bandwidth " + "for new device state.\n"); + ret = -ENOSPC; + /* FIXME: can we go back to the old state? */ + break; + case COMP_TRB_ERR: + /* the HCD set up something wrong */ + dev_warn(&udev->dev, "ERROR: Endpoint drop flag = 0, add flag = 1, " + "and endpoint is not disabled.\n"); + ret = -EINVAL; + break; + case COMP_SUCCESS: + dev_dbg(&udev->dev, "Successful Endpoint Configure command\n"); + break; + default: + xhci_err(xhci, "ERROR: unexpected command completion " + "code 0x%x.\n", virt_dev->cmd_status); + ret = -EINVAL; + break; + } + if (ret) { + /* Callee should call reset_bandwidth() */ + spin_unlock_irqrestore(&xhci->lock, flags); + return ret; + } + + xhci_dbg(xhci, "Output context after successful config ep cmd:\n"); + xhci_dbg_ctx(xhci, virt_dev->out_ctx, virt_dev->out_ctx_dma, + LAST_CTX_TO_EP_NUM(virt_dev->in_ctx->slot.dev_info)); + + xhci_zero_in_ctx(virt_dev); + /* Free any old rings */ + for (i = 1; i < 31; ++i) { + if (virt_dev->new_ep_rings[i]) { + xhci_ring_free(xhci, virt_dev->ep_rings[i]); + virt_dev->ep_rings[i] = virt_dev->new_ep_rings[i]; + virt_dev->new_ep_rings[i] = NULL; + } + } + + spin_unlock_irqrestore(&xhci->lock, flags); + + return ret; +} + +void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) +{ + unsigned long flags; + struct xhci_hcd *xhci; + struct xhci_virt_device *virt_dev; + int i, ret; + + ret = xhci_check_args(hcd, udev, NULL, 0, __func__); + if (ret <= 0) + return; + xhci = hcd_to_xhci(hcd); + + spin_lock_irqsave(&xhci->lock, flags); + if (!xhci->devs || !xhci->devs[udev->slot_id]) { + xhci_warn(xhci, "xHCI %s called with unaddressed device\n", + __func__); + spin_unlock_irqrestore(&xhci->lock, flags); + return; + } + xhci_dbg(xhci, "%s called for udev %#x\n", __func__, (unsigned int) udev); + virt_dev = xhci->devs[udev->slot_id]; + /* Free any rings allocated for added endpoints */ + for (i = 0; i < 31; ++i) { + if (virt_dev->new_ep_rings[i]) { + xhci_ring_free(xhci, virt_dev->new_ep_rings[i]); + virt_dev->new_ep_rings[i] = NULL; + } + } + xhci_zero_in_ctx(virt_dev); + spin_unlock_irqrestore(&xhci->lock, flags); +} + /* * At this point, the struct usb_device is about to go away, the device has * disconnected, and all traffic has been stopped and the endpoints have been @@ -783,7 +1146,12 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) * address given back to us by the HC. */ udev->devnum = (virt_dev->out_ctx->slot.dev_state & DEV_ADDR_MASK) + 1; - /* FIXME: Zero the input context control for later use? */ + /* Zero the input context control for later use */ + virt_dev->in_ctx->add_flags = 0; + virt_dev->in_ctx->drop_flags = 0; + /* Mirror flags in the output context for future ep enable/disable */ + virt_dev->out_ctx->add_flags = SLOT_FLAG | EP0_FLAG; + virt_dev->out_ctx->drop_flags = 0; spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg(xhci, "Device address = %d\n", udev->devnum); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 6ff2e298bff8..8cd55f03ea26 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -103,7 +103,7 @@ static void xhci_link_segments(struct xhci_hcd *xhci, struct xhci_segment *prev, } /* XXX: Do we need the hcd structure in all these functions? */ -static void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring) +void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring) { struct xhci_segment *seg; struct xhci_segment *first_seg; @@ -257,6 +257,8 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, if (!dev->ep_rings[0]) goto fail; + init_completion(&dev->cmd_completion); + /* * Point to output device context in dcbaa; skip the output control * context, which is eight 32 bit fields (or 32 bytes long) @@ -366,6 +368,176 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud return 0; } +/* Return the polling or NAK interval. + * + * The polling interval is expressed in "microframes". If xHCI's Interval field + * is set to N, it will service the endpoint every 2^(Interval)*125us. + * + * The NAK interval is one NAK per 1 to 255 microframes, or no NAKs if interval + * is set to 0. + */ +static inline unsigned int xhci_get_endpoint_interval(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + unsigned int interval = 0; + + switch (udev->speed) { + case USB_SPEED_HIGH: + /* Max NAK rate */ + if (usb_endpoint_xfer_control(&ep->desc) || + usb_endpoint_xfer_bulk(&ep->desc)) + interval = ep->desc.bInterval; + /* Fall through - SS and HS isoc/int have same decoding */ + case USB_SPEED_SUPER: + if (usb_endpoint_xfer_int(&ep->desc) || + usb_endpoint_xfer_isoc(&ep->desc)) { + if (ep->desc.bInterval == 0) + interval = 0; + else + interval = ep->desc.bInterval - 1; + if (interval > 15) + interval = 15; + if (interval != ep->desc.bInterval + 1) + dev_warn(&udev->dev, "ep %#x - rounding interval to %d microframes\n", + ep->desc.bEndpointAddress, 1 << interval); + } + break; + /* Convert bInterval (in 1-255 frames) to microframes and round down to + * nearest power of 2. + */ + case USB_SPEED_FULL: + case USB_SPEED_LOW: + if (usb_endpoint_xfer_int(&ep->desc) || + usb_endpoint_xfer_isoc(&ep->desc)) { + interval = fls(8*ep->desc.bInterval) - 1; + if (interval > 10) + interval = 10; + if (interval < 3) + interval = 3; + if ((1 << interval) != 8*ep->desc.bInterval) + dev_warn(&udev->dev, "ep %#x - rounding interval to %d microframes\n", + ep->desc.bEndpointAddress, 1 << interval); + } + break; + default: + BUG(); + } + return EP_INTERVAL(interval); +} + +static inline u32 xhci_get_endpoint_type(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + int in; + u32 type; + + in = usb_endpoint_dir_in(&ep->desc); + if (usb_endpoint_xfer_control(&ep->desc)) { + type = EP_TYPE(CTRL_EP); + } else if (usb_endpoint_xfer_bulk(&ep->desc)) { + if (in) + type = EP_TYPE(BULK_IN_EP); + else + type = EP_TYPE(BULK_OUT_EP); + } else if (usb_endpoint_xfer_isoc(&ep->desc)) { + if (in) + type = EP_TYPE(ISOC_IN_EP); + else + type = EP_TYPE(ISOC_OUT_EP); + } else if (usb_endpoint_xfer_int(&ep->desc)) { + if (in) + type = EP_TYPE(INT_IN_EP); + else + type = EP_TYPE(INT_OUT_EP); + } else { + BUG(); + } + return type; +} + +int xhci_endpoint_init(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + unsigned int ep_index; + struct xhci_ep_ctx *ep_ctx; + struct xhci_ring *ep_ring; + unsigned int max_packet; + unsigned int max_burst; + + ep_index = xhci_get_endpoint_index(&ep->desc); + ep_ctx = &virt_dev->in_ctx->ep[ep_index]; + + /* Set up the endpoint ring */ + virt_dev->new_ep_rings[ep_index] = xhci_ring_alloc(xhci, 1, true, GFP_KERNEL); + if (!virt_dev->new_ep_rings[ep_index]) + return -ENOMEM; + ep_ring = virt_dev->new_ep_rings[ep_index]; + ep_ctx->deq[1] = 0; + ep_ctx->deq[0] = ep_ring->first_seg->dma | ep_ring->cycle_state; + + ep_ctx->ep_info = xhci_get_endpoint_interval(udev, ep); + + /* FIXME dig Mult and streams info out of ep companion desc */ + + /* Allow 3 retries for everything but isoc */ + if (!usb_endpoint_xfer_isoc(&ep->desc)) + ep_ctx->ep_info2 = ERROR_COUNT(3); + else + ep_ctx->ep_info2 = ERROR_COUNT(0); + + ep_ctx->ep_info2 |= xhci_get_endpoint_type(udev, ep); + + /* Set the max packet size and max burst */ + switch (udev->speed) { + case USB_SPEED_SUPER: + max_packet = ep->desc.wMaxPacketSize; + ep_ctx->ep_info2 |= MAX_PACKET(max_packet); + /* FIXME dig out burst from ep companion desc */ + break; + case USB_SPEED_HIGH: + /* bits 11:12 specify the number of additional transaction + * opportunities per microframe (USB 2.0, section 9.6.6) + */ + if (usb_endpoint_xfer_isoc(&ep->desc) || + usb_endpoint_xfer_int(&ep->desc)) { + max_burst = (ep->desc.wMaxPacketSize & 0x1800) >> 11; + ep_ctx->ep_info2 |= MAX_BURST(max_burst); + } + /* Fall through */ + case USB_SPEED_FULL: + case USB_SPEED_LOW: + max_packet = ep->desc.wMaxPacketSize & 0x3ff; + ep_ctx->ep_info2 |= MAX_PACKET(max_packet); + break; + default: + BUG(); + } + /* FIXME Debug endpoint context */ + return 0; +} + +void xhci_endpoint_zero(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + struct usb_host_endpoint *ep) +{ + unsigned int ep_index; + struct xhci_ep_ctx *ep_ctx; + + ep_index = xhci_get_endpoint_index(&ep->desc); + ep_ctx = &virt_dev->in_ctx->ep[ep_index]; + + ep_ctx->ep_info = 0; + ep_ctx->ep_info2 = 0; + ep_ctx->deq[1] = 0; + ep_ctx->deq[0] = 0; + ep_ctx->tx_info = 0; + /* Don't free the endpoint ring until the set interface or configuration + * request succeeds. + */ +} + void xhci_mem_cleanup(struct xhci_hcd *xhci) { struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index ff9a4ef22338..1462709e26c0 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -115,6 +115,10 @@ static const struct hc_driver xhci_pci_hc_driver = { .urb_dequeue = xhci_urb_dequeue, .alloc_dev = xhci_alloc_dev, .free_dev = xhci_free_dev, + .add_endpoint = xhci_add_endpoint, + .drop_endpoint = xhci_drop_endpoint, + .check_bandwidth = xhci_check_bandwidth, + .reset_bandwidth = xhci_reset_bandwidth, .address_device = xhci_address_device, /* diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index f04162ae4374..b4ccf0d72c17 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -281,6 +281,10 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, if (xhci->devs[slot_id]) xhci_free_virt_device(xhci, slot_id); break; + case TRB_TYPE(TRB_CONFIG_EP): + xhci->devs[slot_id]->cmd_status = GET_COMP_CODE(event->status); + complete(&xhci->devs[slot_id]->cmd_completion); + break; case TRB_TYPE(TRB_ADDR_DEV): xhci->devs[slot_id]->cmd_status = GET_COMP_CODE(event->status); complete(&xhci->addr_dev); @@ -809,3 +813,10 @@ int queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_ return queue_command(xhci, in_ctx_ptr, 0, 0, TRB_TYPE(TRB_ADDR_DEV) | SLOT_ID_FOR_TRB(slot_id)); } + +/* Queue a configure endpoint command TRB */ +int queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id) +{ + return queue_command(xhci, in_ctx_ptr, 0, 0, + TRB_TYPE(TRB_CONFIG_EP) | SLOT_ID_FOR_TRB(slot_id)); +} diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index fc8dcd2aa770..1a6fd997c343 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -486,8 +486,6 @@ struct xhci_slot_ctx { #define LAST_CTX_MASK (0x1f << 27) #define LAST_CTX(p) ((p) << 27) #define LAST_CTX_TO_EP_NUM(p) (((p) >> 27) - 1) -/* Plus one for the slot context flag */ -#define EPI_TO_FLAG(p) (1 << ((p) + 1)) #define SLOT_FLAG (1 << 0) #define EP0_FLAG (1 << 1) @@ -566,7 +564,7 @@ struct xhci_ep_ctx { /* bits 10:14 are Max Primary Streams */ /* bit 15 is Linear Stream Array */ /* Interval - period between requests to an endpoint - 125u increments. */ -#define EP_INTERVAL (0xff << 16) +#define EP_INTERVAL(p) ((p & 0xff) << 16) /* ep_info2 bitmasks */ /* @@ -626,6 +624,11 @@ struct xhci_virt_device { dma_addr_t in_ctx_dma; /* FIXME when stream support is added */ struct xhci_ring *ep_rings[31]; + /* Temporary storage in case the configure endpoint command fails and we + * have to restore the device state to the previous state + */ + struct xhci_ring *new_ep_rings[31]; + struct completion cmd_completion; /* Status of the last command issued for this device */ u32 cmd_status; }; @@ -1075,6 +1078,10 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id); int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, struct usb_device *udev, gfp_t flags); int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *udev); unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc); +unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc); +void xhci_endpoint_zero(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, struct usb_host_endpoint *ep); +int xhci_endpoint_init(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, struct usb_device *udev, struct usb_host_endpoint *ep); +void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring); #ifdef CONFIG_PCI /* xHCI PCI glue */ @@ -1096,6 +1103,10 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev); int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev); int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags); int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); +int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep); +int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep); +int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); +void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); /* xHCI ring, segment, TRB, and TD functions */ dma_addr_t trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); @@ -1106,6 +1117,7 @@ void set_hc_event_deq(struct xhci_hcd *xhci); int queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id); int queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); int queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); +int queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); /* xHCI roothub code */ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, -- cgit v1.2.3 From b10de142119a676552df3f0d2e3a9d647036c26a Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:58:50 -0700 Subject: USB: xhci: Bulk transfer support Allow device drivers to submit URBs to bulk endpoints on devices under an xHCI host controller. Share code between the control and bulk enqueueing functions when it makes sense. To get the best performance out of bulk transfers, SuperSpeed devices must have the bMaxBurst size copied from their endpoint companion controller into the xHCI device context. This allows the host controller to "burst" up to 16 packets before it has to wait for the device to acknowledge the first packet. The buffers in Transfer Request Blocks (TRBs) can cross page boundaries, but they cannot cross 64KB boundaries. The buffer must be broken into multiple TRBs if a 64KB boundary is crossed. The sum of buffer lengths in all the TRBs in a Transfer Descriptor (TD) cannot exceed 64MB. To work around this, the enqueueing code must enqueue multiple TDs. The transfer event handler may incorrectly give back the URB in this case, if it gets a transfer event that points somewhere in the first TD. FIXME later. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-hcd.c | 13 +-- drivers/usb/host/xhci-mem.c | 4 +- drivers/usb/host/xhci-ring.c | 270 ++++++++++++++++++++++++++++++++++++++----- drivers/usb/host/xhci.h | 4 + 4 files changed, 254 insertions(+), 37 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 50ab525f65be..e5fbdcdbf676 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -589,12 +589,6 @@ int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) slot_id = urb->dev->slot_id; ep_index = xhci_get_endpoint_index(&urb->ep->desc); - /* Only support ep 0 control transfers for now */ - if (ep_index != 0) { - xhci_dbg(xhci, "WARN: urb submitted to unsupported ep %x\n", - urb->ep->desc.bEndpointAddress); - return -ENOSYS; - } spin_lock_irqsave(&xhci->lock, flags); if (!xhci->devs || !xhci->devs[slot_id]) { @@ -608,7 +602,12 @@ int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) ret = -ESHUTDOWN; goto exit; } - ret = queue_ctrl_tx(xhci, mem_flags, urb, slot_id, ep_index); + if (usb_endpoint_xfer_control(&urb->ep->desc)) + ret = queue_ctrl_tx(xhci, mem_flags, urb, slot_id, ep_index); + else if (usb_endpoint_xfer_bulk(&urb->ep->desc)) + ret = queue_bulk_tx(xhci, mem_flags, urb, slot_id, ep_index); + else + ret = -EINVAL; exit: spin_unlock_irqrestore(&xhci->lock, flags); return ret; diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 8cd55f03ea26..617db9c37770 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -494,7 +494,9 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, case USB_SPEED_SUPER: max_packet = ep->desc.wMaxPacketSize; ep_ctx->ep_info2 |= MAX_PACKET(max_packet); - /* FIXME dig out burst from ep companion desc */ + /* dig out max burst from ep companion desc */ + max_packet = ep->ep_comp->desc.bMaxBurst; + ep_ctx->ep_info2 |= MAX_BURST(max_packet); break; case USB_SPEED_HIGH: /* bits 11:12 specify the number of additional transaction diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index b4ccf0d72c17..3364381ee6ca 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -395,7 +395,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, dma_addr_t event_dma; struct xhci_segment *event_seg; union xhci_trb *event_trb; - struct urb *urb = NULL; + struct urb *urb; int status = -EINPROGRESS; xdev = xhci->devs[TRB_TO_SLOT_ID(event->flags)]; @@ -437,7 +437,46 @@ static int handle_tx_event(struct xhci_hcd *xhci, return -ESHUTDOWN; } event_trb = &event_seg->trbs[(event_dma - event_seg->dma) / sizeof(*event_trb)]; - + xhci_dbg(xhci, "Event TRB with TRB type ID %u\n", + (unsigned int) (event->flags & TRB_TYPE_BITMASK)>>10); + xhci_dbg(xhci, "Offset 0x00 (buffer[0]) = 0x%x\n", + (unsigned int) event->buffer[0]); + xhci_dbg(xhci, "Offset 0x04 (buffer[0]) = 0x%x\n", + (unsigned int) event->buffer[1]); + xhci_dbg(xhci, "Offset 0x08 (transfer length) = 0x%x\n", + (unsigned int) event->transfer_len); + xhci_dbg(xhci, "Offset 0x0C (flags) = 0x%x\n", + (unsigned int) event->flags); + + /* Look for common error cases */ + switch (GET_COMP_CODE(event->transfer_len)) { + /* Skip codes that require special handling depending on + * transfer type + */ + case COMP_SUCCESS: + case COMP_SHORT_TX: + break; + case COMP_STALL: + xhci_warn(xhci, "WARN: Stalled endpoint\n"); + status = -EPIPE; + break; + case COMP_TRB_ERR: + xhci_warn(xhci, "WARN: TRB error on endpoint\n"); + status = -EILSEQ; + break; + case COMP_TX_ERR: + xhci_warn(xhci, "WARN: transfer error on endpoint\n"); + status = -EPROTO; + break; + case COMP_DB_ERR: + xhci_warn(xhci, "WARN: HC couldn't access mem fast enough\n"); + status = -ENOSR; + break; + default: + xhci_warn(xhci, "ERROR Unknown event condition, HC probably busted\n"); + urb = NULL; + goto cleanup; + } /* Now update the urb's actual_length and give back to the core */ /* Was this a control transfer? */ if (usb_endpoint_xfer_control(&td->urb->ep->desc)) { @@ -459,25 +498,9 @@ static int handle_tx_event(struct xhci_hcd *xhci, xhci_warn(xhci, "WARN: short transfer on control ep\n"); status = -EREMOTEIO; break; - case COMP_STALL: - xhci_warn(xhci, "WARN: Stalled control ep\n"); - status = -EPIPE; - break; - case COMP_TRB_ERR: - xhci_warn(xhci, "WARN: TRB error on control ep\n"); - status = -EILSEQ; - break; - case COMP_TX_ERR: - xhci_warn(xhci, "WARN: transfer error on control ep\n"); - status = -EPROTO; - break; - case COMP_DB_ERR: - xhci_warn(xhci, "WARN: HC couldn't access mem fast enough on control TX\n"); - status = -ENOSR; - break; default: - xhci_dbg(xhci, "ERROR Unknown event condition, HC probably busted\n"); - goto cleanup; + /* Others already handled above */ + break; } /* * Did we transfer any data, despite the errors that might have @@ -493,21 +516,90 @@ static int handle_tx_event(struct xhci_hcd *xhci, TRB_LEN(event->transfer_len); } } - while (ep_ring->dequeue != td->last_trb) - inc_deq(xhci, ep_ring, false); - inc_deq(xhci, ep_ring, false); - - /* Clean up the endpoint's TD list */ - urb = td->urb; - list_del(&td->td_list); - kfree(td); } else { - xhci_dbg(xhci, "FIXME do something for non-control transfers\n"); + switch (GET_COMP_CODE(event->transfer_len)) { + case COMP_SUCCESS: + /* Double check that the HW transferred everything. */ + if (event_trb != td->last_trb) { + xhci_warn(xhci, "WARN Successful completion " + "on short TX\n"); + if (td->urb->transfer_flags & URB_SHORT_NOT_OK) + status = -EREMOTEIO; + else + status = 0; + } else { + xhci_dbg(xhci, "Successful bulk transfer!\n"); + status = 0; + } + break; + case COMP_SHORT_TX: + if (td->urb->transfer_flags & URB_SHORT_NOT_OK) + status = -EREMOTEIO; + else + status = 0; + break; + default: + /* Others already handled above */ + break; + } + dev_dbg(&td->urb->dev->dev, + "ep %#x - asked for %d bytes, " + "%d bytes untransferred\n", + td->urb->ep->desc.bEndpointAddress, + td->urb->transfer_buffer_length, + TRB_LEN(event->transfer_len)); + /* Fast path - was this the last TRB in the TD for this URB? */ + if (event_trb == td->last_trb) { + if (TRB_LEN(event->transfer_len) != 0) { + td->urb->actual_length = + td->urb->transfer_buffer_length - + TRB_LEN(event->transfer_len); + if (td->urb->actual_length < 0) { + xhci_warn(xhci, "HC gave bad length " + "of %d bytes left\n", + TRB_LEN(event->transfer_len)); + td->urb->actual_length = 0; + } + if (td->urb->transfer_flags & URB_SHORT_NOT_OK) + status = -EREMOTEIO; + else + status = 0; + } else { + td->urb->actual_length = td->urb->transfer_buffer_length; + /* Ignore a short packet completion if the + * untransferred length was zero. + */ + status = 0; + } + } else { + /* Slow path - walk the list, starting from the first + * TRB to get the actual length transferred + */ + td->urb->actual_length = 0; + while (ep_ring->dequeue != event_trb) { + td->urb->actual_length += TRB_LEN(ep_ring->dequeue->generic.field[2]); + inc_deq(xhci, ep_ring, false); + } + td->urb->actual_length += TRB_LEN(ep_ring->dequeue->generic.field[2]) - + TRB_LEN(event->transfer_len); + + } } + /* Update ring dequeue pointer */ + while (ep_ring->dequeue != td->last_trb) + inc_deq(xhci, ep_ring, false); + inc_deq(xhci, ep_ring, false); + + /* Clean up the endpoint's TD list */ + urb = td->urb; + list_del(&td->td_list); + kfree(td); + urb->hcpriv = NULL; cleanup: inc_deq(xhci, xhci->event_ring, true); set_hc_event_deq(xhci); + /* FIXME for multi-TD URBs (who have buffers bigger than 64MB) */ if (urb) { usb_hcd_unlink_urb_from_ep(xhci_to_hcd(xhci), urb); spin_unlock(&xhci->lock); @@ -666,6 +758,126 @@ int xhci_prepare_transfer(struct xhci_hcd *xhci, return 0; } +/* This is very similar to what ehci-q.c qtd_fill() does */ +int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index) +{ + struct xhci_ring *ep_ring; + struct xhci_td *td; + int num_trbs; + struct xhci_generic_trb *start_trb; + bool first_trb; + int start_cycle; + u32 field; + + int running_total, trb_buff_len, ret; + u64 addr; + + ep_ring = xhci->devs[slot_id]->ep_rings[ep_index]; + + num_trbs = 0; + /* How much data is (potentially) left before the 64KB boundary? */ + running_total = TRB_MAX_BUFF_SIZE - + (urb->transfer_dma & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + + /* If there's some data on this 64KB chunk, or we have to send a + * zero-length transfer, we need at least one TRB + */ + if (running_total != 0 || urb->transfer_buffer_length == 0) + num_trbs++; + /* How many more 64KB chunks to transfer, how many more TRBs? */ + while (running_total < urb->transfer_buffer_length) { + num_trbs++; + running_total += TRB_MAX_BUFF_SIZE; + } + /* FIXME: this doesn't deal with URB_ZERO_PACKET - need one more */ + + if (!in_interrupt()) + dev_dbg(&urb->dev->dev, "ep %#x - urb len = %d, addr = %#x, num_trbs = %d\n", + urb->ep->desc.bEndpointAddress, + urb->transfer_buffer_length, urb->transfer_dma, + num_trbs); + ret = xhci_prepare_transfer(xhci, xhci->devs[slot_id], ep_index, + num_trbs, urb, &td, mem_flags); + if (ret < 0) + return ret; + + /* + * Don't give the first TRB to the hardware (by toggling the cycle bit) + * until we've finished creating all the other TRBs. The ring's cycle + * state may change as we enqueue the other TRBs, so save it too. + */ + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + + running_total = 0; + /* How much data is in the first TRB? */ + addr = (u64) urb->transfer_dma; + trb_buff_len = TRB_MAX_BUFF_SIZE - + (urb->transfer_dma & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + if (urb->transfer_buffer_length < trb_buff_len) + trb_buff_len = urb->transfer_buffer_length; + + first_trb = true; + + /* Queue the first TRB, even if it's zero-length */ + do { + field = 0; + + /* Don't change the cycle bit of the first TRB until later */ + if (first_trb) + first_trb = false; + else + field |= ep_ring->cycle_state; + + /* Chain all the TRBs together; clear the chain bit in the last + * TRB to indicate it's the last TRB in the chain. + */ + if (num_trbs > 1) { + field |= TRB_CHAIN; + } else { + /* FIXME - add check for ZERO_PACKET flag before this */ + td->last_trb = ep_ring->enqueue; + field |= TRB_IOC; + } + queue_trb(xhci, ep_ring, false, + (u32) addr, + (u32) ((u64) addr >> 32), + TRB_LEN(trb_buff_len) | TRB_INTR_TARGET(0), + /* We always want to know if the TRB was short, + * or we won't get an event when it completes. + * (Unless we use event data TRBs, which are a + * waste of space and HC resources.) + */ + field | TRB_ISP | TRB_TYPE(TRB_NORMAL)); + --num_trbs; + running_total += trb_buff_len; + + /* Calculate length for next transfer */ + addr += trb_buff_len; + trb_buff_len = urb->transfer_buffer_length - running_total; + if (trb_buff_len > TRB_MAX_BUFF_SIZE) + trb_buff_len = TRB_MAX_BUFF_SIZE; + } while (running_total < urb->transfer_buffer_length); + + if (num_trbs != 0) + dev_dbg(&urb->dev->dev, "%s - ep %#x - Miscalculated number of " + "TRBs, %d left\n", __FUNCTION__, + urb->ep->desc.bEndpointAddress, num_trbs); + /* + * Pass all the TRBs to the hardware at once and make sure this write + * isn't reordered. + */ + wmb(); + start_trb->field[3] |= start_cycle; + field = xhci_readl(xhci, &xhci->dba->doorbell[slot_id]) & DB_MASK; + xhci_writel(xhci, field | EPI_TO_DB(ep_index), &xhci->dba->doorbell[slot_id]); + /* Flush PCI posted writes */ + xhci_readl(xhci, &xhci->dba->doorbell[slot_id]); + + return 0; +} + /* Caller must have locked xhci->lock */ int queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index) diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 1a6fd997c343..06e07616631f 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -889,6 +889,9 @@ union xhci_trb { */ #define TRBS_PER_SEGMENT 64 #define SEGMENT_SIZE (TRBS_PER_SEGMENT*16) +/* TRB buffer pointers can't cross 64KB boundaries */ +#define TRB_MAX_BUFF_SHIFT 16 +#define TRB_MAX_BUFF_SIZE (1 << TRB_MAX_BUFF_SHIFT) struct xhci_td { struct list_head td_list; @@ -1117,6 +1120,7 @@ void set_hc_event_deq(struct xhci_hcd *xhci); int queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id); int queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); int queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); +int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); int queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); /* xHCI roothub code */ -- cgit v1.2.3 From e04748e3a87271fcf30d383e3780c5d3ee1c1618 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:59:01 -0700 Subject: USB: Push scatter gather lists down to host controller drivers. This is the original patch I created before David Vrabel posted a better patch (http://marc.info/?l=linux-usb&m=123377477209109&w=2) that does basically the same thing. This patch will get replaced with his (modified) patch later. Allow USB device drivers that use usb_sg_init() and usb_sg_wait() to push bulk endpoint scatter gather lists down to the host controller drivers. This allows host controller drivers to more efficiently enqueue these transfers, and allows the xHCI host controller to better take advantage of USB 3.0 "bursts" for bulk endpoints. This patch currently only enables scatter gather lists for bulk endpoints. Other endpoint types that use the usb_sg_* functions will not have their scatter gather lists pushed down to the host controller. For periodic endpoints, we want each scatterlist entry to be a separate transfer. Eventually, HCDs could parse these scatter-gather lists for periodic endpoints also. For now, we use the old code and call usb_submit_urb() for each scatterlist entry. The caller of usb_sg_init() can request that all bytes in the scatter gather list be transferred by passing in a length of zero. Handle that request for a bulk endpoint under xHCI by walking the scatter gather list and calculating the length. We could let the HCD handle a zero length in this case, but I'm not sure if the core layers in between will get confused by this. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hcd.c | 3 +- drivers/usb/core/message.c | 139 +++++++++++++++++++++++++++++---------------- include/linux/usb.h | 2 + 3 files changed, 95 insertions(+), 49 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index b2da4753b12e..1609623ec829 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1239,7 +1239,8 @@ static int map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, /* Map the URB's buffers for DMA access. * Lower level HCD code should use *_dma exclusively, - * unless it uses pio or talks to another transport. + * unless it uses pio or talks to another transport, + * or uses the provided scatter gather list for bulk. */ if (is_root_hub(urb->dev)) return 0; diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 3a2e69ec2f29..2bed83caacb1 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -365,6 +365,7 @@ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev, int i; int urb_flags; int dma; + int use_sg; if (!io || !dev || !sg || usb_pipecontrol(pipe) @@ -392,7 +393,19 @@ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev, if (io->entries <= 0) return io->entries; - io->urbs = kmalloc(io->entries * sizeof *io->urbs, mem_flags); + /* If we're running on an xHCI host controller, queue the whole scatter + * gather list with one call to urb_enqueue(). This is only for bulk, + * as that endpoint type does not care how the data gets broken up + * across frames. + */ + if (usb_pipebulk(pipe) && + bus_to_hcd(dev->bus)->driver->flags & HCD_USB3) { + io->urbs = kmalloc(sizeof *io->urbs, mem_flags); + use_sg = true; + } else { + io->urbs = kmalloc(io->entries * sizeof *io->urbs, mem_flags); + use_sg = false; + } if (!io->urbs) goto nomem; @@ -402,62 +415,92 @@ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev, if (usb_pipein(pipe)) urb_flags |= URB_SHORT_NOT_OK; - for_each_sg(sg, sg, io->entries, i) { - unsigned len; - - io->urbs[i] = usb_alloc_urb(0, mem_flags); - if (!io->urbs[i]) { - io->entries = i; + if (use_sg) { + io->urbs[0] = usb_alloc_urb(0, mem_flags); + if (!io->urbs[0]) { + io->entries = 0; goto nomem; } - io->urbs[i]->dev = NULL; - io->urbs[i]->pipe = pipe; - io->urbs[i]->interval = period; - io->urbs[i]->transfer_flags = urb_flags; - - io->urbs[i]->complete = sg_complete; - io->urbs[i]->context = io; - - /* - * Some systems need to revert to PIO when DMA is temporarily - * unavailable. For their sakes, both transfer_buffer and - * transfer_dma are set when possible. However this can only - * work on systems without: - * - * - HIGHMEM, since DMA buffers located in high memory are - * not directly addressable by the CPU for PIO; - * - * - IOMMU, since dma_map_sg() is allowed to use an IOMMU to - * make virtually discontiguous buffers be "dma-contiguous" - * so that PIO and DMA need diferent numbers of URBs. - * - * So when HIGHMEM or IOMMU are in use, transfer_buffer is NULL - * to prevent stale pointers and to help spot bugs. - */ - if (dma) { - io->urbs[i]->transfer_dma = sg_dma_address(sg); - len = sg_dma_len(sg); + io->urbs[0]->dev = NULL; + io->urbs[0]->pipe = pipe; + io->urbs[0]->interval = period; + io->urbs[0]->transfer_flags = urb_flags; + + io->urbs[0]->complete = sg_complete; + io->urbs[0]->context = io; + /* A length of zero means transfer the whole sg list */ + io->urbs[0]->transfer_buffer_length = length; + if (length == 0) { + for_each_sg(sg, sg, io->entries, i) { + io->urbs[0]->transfer_buffer_length += + sg_dma_len(sg); + } + } + io->urbs[0]->sg = io; + io->urbs[0]->num_sgs = io->entries; + io->entries = 1; + } else { + for_each_sg(sg, sg, io->entries, i) { + unsigned len; + + io->urbs[i] = usb_alloc_urb(0, mem_flags); + if (!io->urbs[i]) { + io->entries = i; + goto nomem; + } + + io->urbs[i]->dev = NULL; + io->urbs[i]->pipe = pipe; + io->urbs[i]->interval = period; + io->urbs[i]->transfer_flags = urb_flags; + + io->urbs[i]->complete = sg_complete; + io->urbs[i]->context = io; + + /* + * Some systems need to revert to PIO when DMA is + * temporarily unavailable. For their sakes, both + * transfer_buffer and transfer_dma are set when + * possible. However this can only work on systems + * without: + * + * - HIGHMEM, since DMA buffers located in high memory + * are not directly addressable by the CPU for PIO; + * + * - IOMMU, since dma_map_sg() is allowed to use an + * IOMMU to make virtually discontiguous buffers be + * "dma-contiguous" so that PIO and DMA need diferent + * numbers of URBs. + * + * So when HIGHMEM or IOMMU are in use, transfer_buffer + * is NULL to prevent stale pointers and to help spot + * bugs. + */ + if (dma) { + io->urbs[i]->transfer_dma = sg_dma_address(sg); + len = sg_dma_len(sg); #if defined(CONFIG_HIGHMEM) || defined(CONFIG_GART_IOMMU) - io->urbs[i]->transfer_buffer = NULL; + io->urbs[i]->transfer_buffer = NULL; #else - io->urbs[i]->transfer_buffer = sg_virt(sg); + io->urbs[i]->transfer_buffer = sg_virt(sg); #endif - } else { - /* hc may use _only_ transfer_buffer */ - io->urbs[i]->transfer_buffer = sg_virt(sg); - len = sg->length; - } + } else { + /* hc may use _only_ transfer_buffer */ + io->urbs[i]->transfer_buffer = sg_virt(sg); + len = sg->length; + } - if (length) { - len = min_t(unsigned, len, length); - length -= len; - if (length == 0) - io->entries = i + 1; + if (length) { + len = min_t(unsigned, len, length); + length -= len; + if (length == 0) + io->entries = i + 1; + } + io->urbs[i]->transfer_buffer_length = len; } - io->urbs[i]->transfer_buffer_length = len; + io->urbs[--i]->transfer_flags &= ~URB_NO_INTERRUPT; } - io->urbs[--i]->transfer_flags &= ~URB_NO_INTERRUPT; /* transaction state */ io->count = io->entries; diff --git a/include/linux/usb.h b/include/linux/usb.h index 13bced521b83..0a1819a64974 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1198,6 +1198,8 @@ struct urb { unsigned int transfer_flags; /* (in) URB_SHORT_NOT_OK | ...*/ void *transfer_buffer; /* (in) associated data buffer */ dma_addr_t transfer_dma; /* (in) dma addr for transfer_buffer */ + struct usb_sg_request *sg; /* (in) scatter gather buffer list */ + int num_sgs; /* (in) number of entries in the sg list */ u32 transfer_buffer_length; /* (in) data buffer length */ u32 actual_length; /* (return) actual transfer length */ unsigned char *setup_packet; /* (in) setup packet (control only) */ -- cgit v1.2.3 From 8a96c052283e68fe91a6c657c175b39bfed80bed Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Mon, 27 Apr 2009 19:59:19 -0700 Subject: USB: xhci: Scatter gather list support for bulk transfers. Add support for bulk URBs that pass scatter gather lists to xHCI. This allows xHCI to more efficiently enqueue these transfers, and allows the host controller to take advantage of USB 3.0 "bursts" for bulk endpoints. Use requested length to calculate the number of TRBs needed for a scatter gather list transfer, instead of using the number of sglist entries. The application can pass down a scatter gather list that is bigger than it needs for the requested transfer. Scatter gather entries can cross 64KB boundaries, so be careful to setup TRBs such that no buffer crosses a 64KB boundary. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-ring.c | 245 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 217 insertions(+), 28 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 3364381ee6ca..c948288042e2 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -64,6 +64,7 @@ * endpoint rings; it generates events on the event ring for these. */ +#include <linux/scatterlist.h> #include "xhci.h" /* @@ -758,6 +759,211 @@ int xhci_prepare_transfer(struct xhci_hcd *xhci, return 0; } +unsigned int count_sg_trbs_needed(struct xhci_hcd *xhci, struct urb *urb) +{ + int num_sgs, num_trbs, running_total, temp, i; + struct scatterlist *sg; + + sg = NULL; + num_sgs = urb->num_sgs; + temp = urb->transfer_buffer_length; + + xhci_dbg(xhci, "count sg list trbs: \n"); + num_trbs = 0; + for_each_sg(urb->sg->sg, sg, num_sgs, i) { + unsigned int previous_total_trbs = num_trbs; + unsigned int len = sg_dma_len(sg); + + /* Scatter gather list entries may cross 64KB boundaries */ + running_total = TRB_MAX_BUFF_SIZE - + (sg_dma_address(sg) & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + if (running_total != 0) + num_trbs++; + + /* How many more 64KB chunks to transfer, how many more TRBs? */ + while (running_total < sg_dma_len(sg)) { + num_trbs++; + running_total += TRB_MAX_BUFF_SIZE; + } + xhci_dbg(xhci, " sg #%d: dma = %#x, len = %#x (%d), num_trbs = %d\n", + i, sg_dma_address(sg), len, len, + num_trbs - previous_total_trbs); + + len = min_t(int, len, temp); + temp -= len; + if (temp == 0) + break; + } + xhci_dbg(xhci, "\n"); + if (!in_interrupt()) + dev_dbg(&urb->dev->dev, "ep %#x - urb len = %d, sglist used, num_trbs = %d\n", + urb->ep->desc.bEndpointAddress, + urb->transfer_buffer_length, + num_trbs); + return num_trbs; +} + +void check_trb_math(struct urb *urb, int num_trbs, int running_total) +{ + if (num_trbs != 0) + dev_dbg(&urb->dev->dev, "%s - ep %#x - Miscalculated number of " + "TRBs, %d left\n", __func__, + urb->ep->desc.bEndpointAddress, num_trbs); + if (running_total != urb->transfer_buffer_length) + dev_dbg(&urb->dev->dev, "%s - ep %#x - Miscalculated tx length, " + "queued %#x (%d), asked for %#x (%d)\n", + __func__, + urb->ep->desc.bEndpointAddress, + running_total, running_total, + urb->transfer_buffer_length, + urb->transfer_buffer_length); +} + +void giveback_first_trb(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index, int start_cycle, + struct xhci_generic_trb *start_trb, struct xhci_td *td) +{ + u32 field; + + /* + * Pass all the TRBs to the hardware at once and make sure this write + * isn't reordered. + */ + wmb(); + start_trb->field[3] |= start_cycle; + field = xhci_readl(xhci, &xhci->dba->doorbell[slot_id]) & DB_MASK; + xhci_writel(xhci, field | EPI_TO_DB(ep_index), + &xhci->dba->doorbell[slot_id]); + /* Flush PCI posted writes */ + xhci_readl(xhci, &xhci->dba->doorbell[slot_id]); +} + +int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + struct urb *urb, int slot_id, unsigned int ep_index) +{ + struct xhci_ring *ep_ring; + unsigned int num_trbs; + struct xhci_td *td; + struct scatterlist *sg; + int num_sgs; + int trb_buff_len, this_sg_len, running_total; + bool first_trb; + u64 addr; + + struct xhci_generic_trb *start_trb; + int start_cycle; + + ep_ring = xhci->devs[slot_id]->ep_rings[ep_index]; + num_trbs = count_sg_trbs_needed(xhci, urb); + num_sgs = urb->num_sgs; + + trb_buff_len = xhci_prepare_transfer(xhci, xhci->devs[slot_id], + ep_index, num_trbs, urb, &td, mem_flags); + if (trb_buff_len < 0) + return trb_buff_len; + /* + * Don't give the first TRB to the hardware (by toggling the cycle bit) + * until we've finished creating all the other TRBs. The ring's cycle + * state may change as we enqueue the other TRBs, so save it too. + */ + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + + running_total = 0; + /* + * How much data is in the first TRB? + * + * There are three forces at work for TRB buffer pointers and lengths: + * 1. We don't want to walk off the end of this sg-list entry buffer. + * 2. The transfer length that the driver requested may be smaller than + * the amount of memory allocated for this scatter-gather list. + * 3. TRBs buffers can't cross 64KB boundaries. + */ + sg = urb->sg->sg; + addr = (u64) sg_dma_address(sg); + this_sg_len = sg_dma_len(sg); + trb_buff_len = TRB_MAX_BUFF_SIZE - + (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + trb_buff_len = min_t(int, trb_buff_len, this_sg_len); + if (trb_buff_len > urb->transfer_buffer_length) + trb_buff_len = urb->transfer_buffer_length; + xhci_dbg(xhci, "First length to xfer from 1st sglist entry = %u\n", + trb_buff_len); + + first_trb = true; + /* Queue the first TRB, even if it's zero-length */ + do { + u32 field = 0; + + /* Don't change the cycle bit of the first TRB until later */ + if (first_trb) + first_trb = false; + else + field |= ep_ring->cycle_state; + + /* Chain all the TRBs together; clear the chain bit in the last + * TRB to indicate it's the last TRB in the chain. + */ + if (num_trbs > 1) { + field |= TRB_CHAIN; + } else { + /* FIXME - add check for ZERO_PACKET flag before this */ + td->last_trb = ep_ring->enqueue; + field |= TRB_IOC; + } + xhci_dbg(xhci, " sg entry: dma = %#x, len = %#x (%d), " + "64KB boundary at %#x, end dma = %#x\n", + (unsigned int) addr, trb_buff_len, trb_buff_len, + (unsigned int) (addr + TRB_MAX_BUFF_SIZE) & ~(TRB_MAX_BUFF_SIZE - 1), + (unsigned int) addr + trb_buff_len); + if (TRB_MAX_BUFF_SIZE - + (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1)) < trb_buff_len) { + xhci_warn(xhci, "WARN: sg dma xfer crosses 64KB boundaries!\n"); + xhci_dbg(xhci, "Next boundary at %#x, end dma = %#x\n", + (unsigned int) (addr + TRB_MAX_BUFF_SIZE) & ~(TRB_MAX_BUFF_SIZE - 1), + (unsigned int) addr + trb_buff_len); + } + queue_trb(xhci, ep_ring, false, + (u32) addr, + (u32) ((u64) addr >> 32), + TRB_LEN(trb_buff_len) | TRB_INTR_TARGET(0), + /* We always want to know if the TRB was short, + * or we won't get an event when it completes. + * (Unless we use event data TRBs, which are a + * waste of space and HC resources.) + */ + field | TRB_ISP | TRB_TYPE(TRB_NORMAL)); + --num_trbs; + running_total += trb_buff_len; + + /* Calculate length for next transfer -- + * Are we done queueing all the TRBs for this sg entry? + */ + this_sg_len -= trb_buff_len; + if (this_sg_len == 0) { + --num_sgs; + if (num_sgs == 0) + break; + sg = sg_next(sg); + addr = (u64) sg_dma_address(sg); + this_sg_len = sg_dma_len(sg); + } else { + addr += trb_buff_len; + } + + trb_buff_len = TRB_MAX_BUFF_SIZE - + (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + trb_buff_len = min_t(int, trb_buff_len, this_sg_len); + if (running_total + trb_buff_len > urb->transfer_buffer_length) + trb_buff_len = + urb->transfer_buffer_length - running_total; + } while (running_total < urb->transfer_buffer_length); + + check_trb_math(urb, num_trbs, running_total); + giveback_first_trb(xhci, slot_id, ep_index, start_cycle, start_trb, td); + return 0; +} + /* This is very similar to what ehci-q.c qtd_fill() does */ int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index) @@ -773,6 +979,9 @@ int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, int running_total, trb_buff_len, ret; u64 addr; + if (urb->sg) + return queue_bulk_sg_tx(xhci, mem_flags, urb, slot_id, ep_index); + ep_ring = xhci->devs[slot_id]->ep_rings[ep_index]; num_trbs = 0; @@ -793,10 +1002,13 @@ int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, /* FIXME: this doesn't deal with URB_ZERO_PACKET - need one more */ if (!in_interrupt()) - dev_dbg(&urb->dev->dev, "ep %#x - urb len = %d, addr = %#x, num_trbs = %d\n", + dev_dbg(&urb->dev->dev, "ep %#x - urb len = %#x (%d), addr = %#x, num_trbs = %d\n", urb->ep->desc.bEndpointAddress, - urb->transfer_buffer_length, urb->transfer_dma, + urb->transfer_buffer_length, + urb->transfer_buffer_length, + urb->transfer_dma, num_trbs); + ret = xhci_prepare_transfer(xhci, xhci->devs[slot_id], ep_index, num_trbs, urb, &td, mem_flags); if (ret < 0) @@ -860,21 +1072,8 @@ int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, trb_buff_len = TRB_MAX_BUFF_SIZE; } while (running_total < urb->transfer_buffer_length); - if (num_trbs != 0) - dev_dbg(&urb->dev->dev, "%s - ep %#x - Miscalculated number of " - "TRBs, %d left\n", __FUNCTION__, - urb->ep->desc.bEndpointAddress, num_trbs); - /* - * Pass all the TRBs to the hardware at once and make sure this write - * isn't reordered. - */ - wmb(); - start_trb->field[3] |= start_cycle; - field = xhci_readl(xhci, &xhci->dba->doorbell[slot_id]) & DB_MASK; - xhci_writel(xhci, field | EPI_TO_DB(ep_index), &xhci->dba->doorbell[slot_id]); - /* Flush PCI posted writes */ - xhci_readl(xhci, &xhci->dba->doorbell[slot_id]); - + check_trb_math(urb, num_trbs, running_total); + giveback_first_trb(xhci, slot_id, ep_index, start_cycle, start_trb, td); return 0; } @@ -965,17 +1164,7 @@ int queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, /* Event on completion */ field | TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state); - /* - * Pass all the TRBs to the hardware at once and make sure this write - * isn't reordered. - */ - wmb(); - start_trb->field[3] |= start_cycle; - field = xhci_readl(xhci, &xhci->dba->doorbell[slot_id]) & DB_MASK; - xhci_writel(xhci, field | EPI_TO_DB(ep_index), &xhci->dba->doorbell[slot_id]); - /* Flush PCI posted writes */ - xhci_readl(xhci, &xhci->dba->doorbell[slot_id]); - + giveback_first_trb(xhci, slot_id, ep_index, start_cycle, start_trb, td); return 0; } -- cgit v1.2.3 From ae636747146ea97efa18e04576acd3416e2514f5 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Wed, 29 Apr 2009 19:02:31 -0700 Subject: USB: xhci: URB cancellation support. Add URB cancellation support to the xHCI host controller driver. This currently supports cancellation for endpoints that do not have streams enabled. An URB is represented by a number of Transaction Request Buffers (TRBs), that are chained together to make one (or more) Transaction Descriptors (TDs) on an endpoint ring. The ring is comprised of contiguous segments, linked together with Link TRBs (which may or may not be chained into a TD). To cancel an URB, we must stop the endpoint ring, make the hardware skip over the TDs in the URB (either by turning them into No-op TDs, or by moving the hardware's ring dequeue pointer past the last TRB in the last TD), and then restart the ring. There are times when we must drop the xHCI lock during this process, like when we need to complete cancelled URBs. We must ensure that additional URBs can be marked as cancelled, and that new URBs can be enqueued (since the URB completion handlers can do either). The new endpoint ring variables cancels_pending and state (which can only be modified while holding the xHCI lock) ensure that future cancellation and enqueueing do not interrupt any pending cancellation code. To facilitate cancellation, we must keep track of the starting ring segment, first TRB, and last TRB for each URB. We also need to keep track of the list of TDs that have been marked as cancelled, separate from the list of TDs that are queued for this endpoint. The new variables and cancellation list are stored in the xhci_td structure. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-hcd.c | 64 +++++- drivers/usb/host/xhci-mem.c | 1 + drivers/usb/host/xhci-ring.c | 491 ++++++++++++++++++++++++++++++++++++++++--- drivers/usb/host/xhci.h | 31 ++- 4 files changed, 545 insertions(+), 42 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index e5fbdcdbf676..36e440ce88e5 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -613,12 +613,70 @@ exit: return ret; } -/* Remove from hardware lists - * completions normally happen asynchronously +/* + * Remove the URB's TD from the endpoint ring. This may cause the HC to stop + * USB transfers, potentially stopping in the middle of a TRB buffer. The HC + * should pick up where it left off in the TD, unless a Set Transfer Ring + * Dequeue Pointer is issued. + * + * The TRBs that make up the buffers for the canceled URB will be "removed" from + * the ring. Since the ring is a contiguous structure, they can't be physically + * removed. Instead, there are two options: + * + * 1) If the HC is in the middle of processing the URB to be canceled, we + * simply move the ring's dequeue pointer past those TRBs using the Set + * Transfer Ring Dequeue Pointer command. This will be the common case, + * when drivers timeout on the last submitted URB and attempt to cancel. + * + * 2) If the HC is in the middle of a different TD, we turn the TRBs into a + * series of 1-TRB transfer no-op TDs. (No-ops shouldn't be chained.) The + * HC will need to invalidate the any TRBs it has cached after the stop + * endpoint command, as noted in the xHCI 0.95 errata. + * + * 3) The TD may have completed by the time the Stop Endpoint Command + * completes, so software needs to handle that case too. + * + * This function should protect against the TD enqueueing code ringing the + * doorbell while this code is waiting for a Stop Endpoint command to complete. + * It also needs to account for multiple cancellations on happening at the same + * time for the same endpoint. + * + * Note that this function can be called in any context, or so says + * usb_hcd_unlink_urb() */ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) { - return -ENOSYS; + unsigned long flags; + int ret; + struct xhci_hcd *xhci; + struct xhci_td *td; + unsigned int ep_index; + struct xhci_ring *ep_ring; + + xhci = hcd_to_xhci(hcd); + spin_lock_irqsave(&xhci->lock, flags); + /* Make sure the URB hasn't completed or been unlinked already */ + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret || !urb->hcpriv) + goto done; + + xhci_dbg(xhci, "Cancel URB 0x%x\n", (unsigned int) urb); + ep_index = xhci_get_endpoint_index(&urb->ep->desc); + ep_ring = xhci->devs[urb->dev->slot_id]->ep_rings[ep_index]; + td = (struct xhci_td *) urb->hcpriv; + + ep_ring->cancels_pending++; + list_add_tail(&td->cancelled_td_list, &ep_ring->cancelled_td_list); + /* Queue a stop endpoint command, but only if this is + * the first cancellation to be handled. + */ + if (ep_ring->cancels_pending == 1) { + queue_stop_endpoint(xhci, urb->dev->slot_id, ep_index); + ring_cmd_db(xhci); + } +done: + spin_unlock_irqrestore(&xhci->lock, flags); + return ret; } /* Drop an endpoint from a new bandwidth configuration for this device. diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 617db9c37770..e81d10a653ef 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -142,6 +142,7 @@ static struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, return 0; INIT_LIST_HEAD(&ring->td_list); + INIT_LIST_HEAD(&ring->cancelled_td_list); if (num_segs == 0) return ring; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index c948288042e2..f967a6df83c7 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -112,6 +112,23 @@ static inline int last_trb(struct xhci_hcd *xhci, struct xhci_ring *ring, return (trb->link.control & TRB_TYPE_BITMASK) == TRB_TYPE(TRB_LINK); } +/* Updates trb to point to the next TRB in the ring, and updates seg if the next + * TRB is in a new segment. This does not skip over link TRBs, and it does not + * effect the ring dequeue or enqueue pointers. + */ +static void next_trb(struct xhci_hcd *xhci, + struct xhci_ring *ring, + struct xhci_segment **seg, + union xhci_trb **trb) +{ + if (last_trb(xhci, ring, *seg, *trb)) { + *seg = (*seg)->next; + *trb = ((*seg)->trbs); + } else { + *trb = (*trb)++; + } +} + /* * See Cycle bit rules. SW is the consumer for the event ring only. * Don't make a ring full of link TRBs. That would be dumb and this would loop. @@ -250,6 +267,344 @@ void ring_cmd_db(struct xhci_hcd *xhci) xhci_readl(xhci, &xhci->dba->doorbell[0]); } +static void ring_ep_doorbell(struct xhci_hcd *xhci, + unsigned int slot_id, + unsigned int ep_index) +{ + struct xhci_ring *ep_ring; + u32 field; + __u32 __iomem *db_addr = &xhci->dba->doorbell[slot_id]; + + ep_ring = xhci->devs[slot_id]->ep_rings[ep_index]; + /* Don't ring the doorbell for this endpoint if there are pending + * cancellations because the we don't want to interrupt processing. + */ + if (!ep_ring->cancels_pending && !(ep_ring->state & SET_DEQ_PENDING)) { + field = xhci_readl(xhci, db_addr) & DB_MASK; + xhci_writel(xhci, field | EPI_TO_DB(ep_index), db_addr); + /* Flush PCI posted writes - FIXME Matthew Wilcox says this + * isn't time-critical and we shouldn't make the CPU wait for + * the flush. + */ + xhci_readl(xhci, db_addr); + } +} + +/* + * Find the segment that trb is in. Start searching in start_seg. + * If we must move past a segment that has a link TRB with a toggle cycle state + * bit set, then we will toggle the value pointed at by cycle_state. + */ +static struct xhci_segment *find_trb_seg( + struct xhci_segment *start_seg, + union xhci_trb *trb, int *cycle_state) +{ + struct xhci_segment *cur_seg = start_seg; + struct xhci_generic_trb *generic_trb; + + while (cur_seg->trbs > trb || + &cur_seg->trbs[TRBS_PER_SEGMENT - 1] < trb) { + generic_trb = &cur_seg->trbs[TRBS_PER_SEGMENT - 1].generic; + if (TRB_TYPE(generic_trb->field[3]) == TRB_LINK && + (generic_trb->field[3] & LINK_TOGGLE)) + *cycle_state = ~(*cycle_state) & 0x1; + cur_seg = cur_seg->next; + if (cur_seg == start_seg) + /* Looped over the entire list. Oops! */ + return 0; + } + return cur_seg; +} + +struct dequeue_state { + struct xhci_segment *new_deq_seg; + union xhci_trb *new_deq_ptr; + int new_cycle_state; +}; + +/* + * Move the xHC's endpoint ring dequeue pointer past cur_td. + * Record the new state of the xHC's endpoint ring dequeue segment, + * dequeue pointer, and new consumer cycle state in state. + * Update our internal representation of the ring's dequeue pointer. + * + * We do this in three jumps: + * - First we update our new ring state to be the same as when the xHC stopped. + * - Then we traverse the ring to find the segment that contains + * the last TRB in the TD. We toggle the xHC's new cycle state when we pass + * any link TRBs with the toggle cycle bit set. + * - Finally we move the dequeue state one TRB further, toggling the cycle bit + * if we've moved it past a link TRB with the toggle cycle bit set. + */ +static void find_new_dequeue_state(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + struct xhci_td *cur_td, struct dequeue_state *state) +{ + struct xhci_virt_device *dev = xhci->devs[slot_id]; + struct xhci_ring *ep_ring = dev->ep_rings[ep_index]; + struct xhci_generic_trb *trb; + + state->new_cycle_state = 0; + state->new_deq_seg = find_trb_seg(cur_td->start_seg, + ep_ring->stopped_trb, + &state->new_cycle_state); + if (!state->new_deq_seg) + BUG(); + /* Dig out the cycle state saved by the xHC during the stop ep cmd */ + state->new_cycle_state = 0x1 & dev->out_ctx->ep[ep_index].deq[0]; + + state->new_deq_ptr = cur_td->last_trb; + state->new_deq_seg = find_trb_seg(state->new_deq_seg, + state->new_deq_ptr, + &state->new_cycle_state); + if (!state->new_deq_seg) + BUG(); + + trb = &state->new_deq_ptr->generic; + if (TRB_TYPE(trb->field[3]) == TRB_LINK && + (trb->field[3] & LINK_TOGGLE)) + state->new_cycle_state = ~(state->new_cycle_state) & 0x1; + next_trb(xhci, ep_ring, &state->new_deq_seg, &state->new_deq_ptr); + + /* Don't update the ring cycle state for the producer (us). */ + ep_ring->dequeue = state->new_deq_ptr; + ep_ring->deq_seg = state->new_deq_seg; +} + +void td_to_noop(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, + struct xhci_td *cur_td) +{ + struct xhci_segment *cur_seg; + union xhci_trb *cur_trb; + + for (cur_seg = cur_td->start_seg, cur_trb = cur_td->first_trb; + true; + next_trb(xhci, ep_ring, &cur_seg, &cur_trb)) { + if ((cur_trb->generic.field[3] & TRB_TYPE_BITMASK) == + TRB_TYPE(TRB_LINK)) { + /* Unchain any chained Link TRBs, but + * leave the pointers intact. + */ + cur_trb->generic.field[3] &= ~TRB_CHAIN; + xhci_dbg(xhci, "Cancel (unchain) link TRB\n"); + xhci_dbg(xhci, "Address = 0x%x (0x%x dma); " + "in seg 0x%x (0x%x dma)\n", + (unsigned int) cur_trb, + trb_virt_to_dma(cur_seg, cur_trb), + (unsigned int) cur_seg, + cur_seg->dma); + } else { + cur_trb->generic.field[0] = 0; + cur_trb->generic.field[1] = 0; + cur_trb->generic.field[2] = 0; + /* Preserve only the cycle bit of this TRB */ + cur_trb->generic.field[3] &= TRB_CYCLE; + cur_trb->generic.field[3] |= TRB_TYPE(TRB_TR_NOOP); + xhci_dbg(xhci, "Cancel TRB 0x%x (0x%x dma) " + "in seg 0x%x (0x%x dma)\n", + (unsigned int) cur_trb, + trb_virt_to_dma(cur_seg, cur_trb), + (unsigned int) cur_seg, + cur_seg->dma); + } + if (cur_trb == cur_td->last_trb) + break; + } +} + +static int queue_set_tr_deq(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index, struct xhci_segment *deq_seg, + union xhci_trb *deq_ptr, u32 cycle_state); + +/* + * When we get a command completion for a Stop Endpoint Command, we need to + * unlink any cancelled TDs from the ring. There are two ways to do that: + * + * 1. If the HW was in the middle of processing the TD that needs to be + * cancelled, then we must move the ring's dequeue pointer past the last TRB + * in the TD with a Set Dequeue Pointer Command. + * 2. Otherwise, we turn all the TRBs in the TD into No-op TRBs (with the chain + * bit cleared) so that the HW will skip over them. + */ +static void handle_stopped_endpoint(struct xhci_hcd *xhci, + union xhci_trb *trb) +{ + unsigned int slot_id; + unsigned int ep_index; + struct xhci_ring *ep_ring; + struct list_head *entry; + struct xhci_td *cur_td = 0; + struct xhci_td *last_unlinked_td; + + struct dequeue_state deq_state; +#ifdef CONFIG_USB_HCD_STAT + ktime_t stop_time = ktime_get(); +#endif + + memset(&deq_state, 0, sizeof(deq_state)); + slot_id = TRB_TO_SLOT_ID(trb->generic.field[3]); + ep_index = TRB_TO_EP_INDEX(trb->generic.field[3]); + ep_ring = xhci->devs[slot_id]->ep_rings[ep_index]; + + if (list_empty(&ep_ring->cancelled_td_list)) + return; + + /* Fix up the ep ring first, so HW stops executing cancelled TDs. + * We have the xHCI lock, so nothing can modify this list until we drop + * it. We're also in the event handler, so we can't get re-interrupted + * if another Stop Endpoint command completes + */ + list_for_each(entry, &ep_ring->cancelled_td_list) { + cur_td = list_entry(entry, struct xhci_td, cancelled_td_list); + xhci_dbg(xhci, "Cancelling TD starting at 0x%x, 0x%x (dma).\n", + (unsigned int) cur_td->first_trb, + trb_virt_to_dma(cur_td->start_seg, cur_td->first_trb)); + /* + * If we stopped on the TD we need to cancel, then we have to + * move the xHC endpoint ring dequeue pointer past this TD. + */ + if (cur_td == ep_ring->stopped_td) + find_new_dequeue_state(xhci, slot_id, ep_index, cur_td, + &deq_state); + else + td_to_noop(xhci, ep_ring, cur_td); + /* + * The event handler won't see a completion for this TD anymore, + * so remove it from the endpoint ring's TD list. Keep it in + * the cancelled TD list for URB completion later. + */ + list_del(&cur_td->td_list); + ep_ring->cancels_pending--; + } + last_unlinked_td = cur_td; + + /* If necessary, queue a Set Transfer Ring Dequeue Pointer command */ + if (deq_state.new_deq_ptr && deq_state.new_deq_seg) { + xhci_dbg(xhci, "Set TR Deq Ptr cmd, new deq seg = 0x%x (0x%x dma), " + "new deq ptr = 0x%x (0x%x dma), new cycle = %u\n", + (unsigned int) deq_state.new_deq_seg, + deq_state.new_deq_seg->dma, + (unsigned int) deq_state.new_deq_ptr, + trb_virt_to_dma(deq_state.new_deq_seg, deq_state.new_deq_ptr), + deq_state.new_cycle_state); + queue_set_tr_deq(xhci, slot_id, ep_index, + deq_state.new_deq_seg, + deq_state.new_deq_ptr, + (u32) deq_state.new_cycle_state); + /* Stop the TD queueing code from ringing the doorbell until + * this command completes. The HC won't set the dequeue pointer + * if the ring is running, and ringing the doorbell starts the + * ring running. + */ + ep_ring->state |= SET_DEQ_PENDING; + ring_cmd_db(xhci); + } else { + /* Otherwise just ring the doorbell to restart the ring */ + ring_ep_doorbell(xhci, slot_id, ep_index); + } + + /* + * Drop the lock and complete the URBs in the cancelled TD list. + * New TDs to be cancelled might be added to the end of the list before + * we can complete all the URBs for the TDs we already unlinked. + * So stop when we've completed the URB for the last TD we unlinked. + */ + do { + cur_td = list_entry(ep_ring->cancelled_td_list.next, + struct xhci_td, cancelled_td_list); + list_del(&cur_td->cancelled_td_list); + + /* Clean up the cancelled URB */ +#ifdef CONFIG_USB_HCD_STAT + hcd_stat_update(xhci->tp_stat, cur_td->urb->actual_length, + ktime_sub(stop_time, cur_td->start_time)); +#endif + cur_td->urb->hcpriv = NULL; + usb_hcd_unlink_urb_from_ep(xhci_to_hcd(xhci), cur_td->urb); + + xhci_dbg(xhci, "Giveback cancelled URB 0x%x\n", + (unsigned int) cur_td->urb); + spin_unlock(&xhci->lock); + /* Doesn't matter what we pass for status, since the core will + * just overwrite it (because the URB has been unlinked). + */ + usb_hcd_giveback_urb(xhci_to_hcd(xhci), cur_td->urb, 0); + kfree(cur_td); + + spin_lock(&xhci->lock); + } while (cur_td != last_unlinked_td); + + /* Return to the event handler with xhci->lock re-acquired */ +} + +/* + * When we get a completion for a Set Transfer Ring Dequeue Pointer command, + * we need to clear the set deq pending flag in the endpoint ring state, so that + * the TD queueing code can ring the doorbell again. We also need to ring the + * endpoint doorbell to restart the ring, but only if there aren't more + * cancellations pending. + */ +static void handle_set_deq_completion(struct xhci_hcd *xhci, + struct xhci_event_cmd *event, + union xhci_trb *trb) +{ + unsigned int slot_id; + unsigned int ep_index; + struct xhci_ring *ep_ring; + struct xhci_virt_device *dev; + + slot_id = TRB_TO_SLOT_ID(trb->generic.field[3]); + ep_index = TRB_TO_EP_INDEX(trb->generic.field[3]); + dev = xhci->devs[slot_id]; + ep_ring = dev->ep_rings[ep_index]; + + if (GET_COMP_CODE(event->status) != COMP_SUCCESS) { + unsigned int ep_state; + unsigned int slot_state; + + switch (GET_COMP_CODE(event->status)) { + case COMP_TRB_ERR: + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd invalid because " + "of stream ID configuration\n"); + break; + case COMP_CTX_STATE: + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd failed due " + "to incorrect slot or ep state.\n"); + ep_state = dev->out_ctx->ep[ep_index].ep_info; + ep_state &= EP_STATE_MASK; + slot_state = dev->out_ctx->slot.dev_state; + slot_state = GET_SLOT_STATE(slot_state); + xhci_dbg(xhci, "Slot state = %u, EP state = %u\n", + slot_state, ep_state); + break; + case COMP_EBADSLT: + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd failed because " + "slot %u was not enabled.\n", slot_id); + break; + default: + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd with unknown " + "completion code of %u.\n", + GET_COMP_CODE(event->status)); + break; + } + /* OK what do we do now? The endpoint state is hosed, and we + * should never get to this point if the synchronization between + * queueing, and endpoint state are correct. This might happen + * if the device gets disconnected after we've finished + * cancelling URBs, which might not be an error... + */ + } else { + xhci_dbg(xhci, "Successful Set TR Deq Ptr cmd, deq[0] = 0x%x, " + "deq[1] = 0x%x.\n", + dev->out_ctx->ep[ep_index].deq[0], + dev->out_ctx->ep[ep_index].deq[1]); + } + + ep_ring->state &= ~SET_DEQ_PENDING; + ring_ep_doorbell(xhci, slot_id, ep_index); +} + + static void handle_cmd_completion(struct xhci_hcd *xhci, struct xhci_event_cmd *event) { @@ -290,6 +645,12 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, xhci->devs[slot_id]->cmd_status = GET_COMP_CODE(event->status); complete(&xhci->addr_dev); break; + case TRB_TYPE(TRB_STOP_RING): + handle_stopped_endpoint(xhci, xhci->cmd_ring->dequeue); + break; + case TRB_TYPE(TRB_SET_DEQ): + handle_set_deq_completion(xhci, event, xhci->cmd_ring->dequeue); + break; case TRB_TYPE(TRB_CMD_NOOP): ++xhci->noops_handled; break; @@ -346,11 +707,9 @@ static struct xhci_segment *trb_in_td( cur_seg = start_seg; do { - /* - * Last TRB is a link TRB (unless we start inserting links in - * the middle, FIXME if you do) - */ - end_seg_dma = trb_virt_to_dma(cur_seg, &start_seg->trbs[TRBS_PER_SEGMENT - 2]); + /* We may get an event for a Link TRB in the middle of a TD */ + end_seg_dma = trb_virt_to_dma(cur_seg, + &start_seg->trbs[TRBS_PER_SEGMENT - 1]); /* If the end TRB isn't in this segment, this is set to 0 */ end_trb_dma = trb_virt_to_dma(cur_seg, end_trb); @@ -396,7 +755,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, dma_addr_t event_dma; struct xhci_segment *event_seg; union xhci_trb *event_trb; - struct urb *urb; + struct urb *urb = 0; int status = -EINPROGRESS; xdev = xhci->devs[TRB_TO_SLOT_ID(event->flags)]; @@ -457,6 +816,12 @@ static int handle_tx_event(struct xhci_hcd *xhci, case COMP_SUCCESS: case COMP_SHORT_TX: break; + case COMP_STOP: + xhci_dbg(xhci, "Stopped on Transfer TRB\n"); + break; + case COMP_STOP_INVAL: + xhci_dbg(xhci, "Stopped on No-op or Link TRB\n"); + break; case COMP_STALL: xhci_warn(xhci, "WARN: Stalled endpoint\n"); status = -EPIPE; @@ -510,11 +875,15 @@ static int handle_tx_event(struct xhci_hcd *xhci, if (event_trb != ep_ring->dequeue) { /* The event was for the status stage */ if (event_trb == td->last_trb) { - td->urb->actual_length = td->urb->transfer_buffer_length; + td->urb->actual_length = + td->urb->transfer_buffer_length; } else { - /* The event was for the data stage */ - td->urb->actual_length = td->urb->transfer_buffer_length - - TRB_LEN(event->transfer_len); + /* Maybe the event was for the data stage? */ + if (GET_COMP_CODE(event->transfer_len) != COMP_STOP_INVAL) + /* We didn't stop on a link TRB in the middle */ + td->urb->actual_length = + td->urb->transfer_buffer_length - + TRB_LEN(event->transfer_len); } } } else { @@ -573,29 +942,55 @@ static int handle_tx_event(struct xhci_hcd *xhci, status = 0; } } else { - /* Slow path - walk the list, starting from the first - * TRB to get the actual length transferred + /* Slow path - walk the list, starting from the dequeue + * pointer, to get the actual length transferred. */ + union xhci_trb *cur_trb; + struct xhci_segment *cur_seg; + td->urb->actual_length = 0; - while (ep_ring->dequeue != event_trb) { - td->urb->actual_length += TRB_LEN(ep_ring->dequeue->generic.field[2]); - inc_deq(xhci, ep_ring, false); + for (cur_trb = ep_ring->dequeue, cur_seg = ep_ring->deq_seg; + cur_trb != event_trb; + next_trb(xhci, ep_ring, &cur_seg, &cur_trb)) { + if (TRB_TYPE(cur_trb->generic.field[3]) != TRB_TR_NOOP && + TRB_TYPE(cur_trb->generic.field[3]) != TRB_LINK) + td->urb->actual_length += + TRB_LEN(cur_trb->generic.field[2]); } - td->urb->actual_length += TRB_LEN(ep_ring->dequeue->generic.field[2]) - - TRB_LEN(event->transfer_len); - + /* If the ring didn't stop on a Link or No-op TRB, add + * in the actual bytes transferred from the Normal TRB + */ + if (GET_COMP_CODE(event->transfer_len) != COMP_STOP_INVAL) + td->urb->actual_length += + TRB_LEN(cur_trb->generic.field[2]) - + TRB_LEN(event->transfer_len); } } - /* Update ring dequeue pointer */ - while (ep_ring->dequeue != td->last_trb) + /* The Endpoint Stop Command completion will take care of + * any stopped TDs. A stopped TD may be restarted, so don't update the + * ring dequeue pointer or take this TD off any lists yet. + */ + if (GET_COMP_CODE(event->transfer_len) == COMP_STOP_INVAL || + GET_COMP_CODE(event->transfer_len) == COMP_STOP) { + ep_ring->stopped_td = td; + ep_ring->stopped_trb = event_trb; + } else { + /* Update ring dequeue pointer */ + while (ep_ring->dequeue != td->last_trb) + inc_deq(xhci, ep_ring, false); inc_deq(xhci, ep_ring, false); - inc_deq(xhci, ep_ring, false); - /* Clean up the endpoint's TD list */ - urb = td->urb; - list_del(&td->td_list); - kfree(td); - urb->hcpriv = NULL; + /* Clean up the endpoint's TD list */ + urb = td->urb; + list_del(&td->td_list); + /* Was this TD slated to be cancelled but completed anyway? */ + if (!list_empty(&td->cancelled_td_list)) { + list_del(&td->cancelled_td_list); + ep_ring->cancels_pending--; + } + kfree(td); + urb->hcpriv = NULL; + } cleanup: inc_deq(xhci, xhci->event_ring, true); set_hc_event_deq(xhci); @@ -744,6 +1139,7 @@ int xhci_prepare_transfer(struct xhci_hcd *xhci, if (!*td) return -ENOMEM; INIT_LIST_HEAD(&(*td)->td_list); + INIT_LIST_HEAD(&(*td)->cancelled_td_list); ret = usb_hcd_link_urb_to_ep(xhci_to_hcd(xhci), urb); if (unlikely(ret)) { @@ -755,6 +1151,8 @@ int xhci_prepare_transfer(struct xhci_hcd *xhci, urb->hcpriv = (void *) (*td); /* Add this TD to the tail of the endpoint ring's TD list */ list_add_tail(&(*td)->td_list, &xdev->ep_rings[ep_index]->td_list); + (*td)->start_seg = xdev->ep_rings[ep_index]->enq_seg; + (*td)->first_trb = xdev->ep_rings[ep_index]->enqueue; return 0; } @@ -823,19 +1221,13 @@ void giveback_first_trb(struct xhci_hcd *xhci, int slot_id, unsigned int ep_index, int start_cycle, struct xhci_generic_trb *start_trb, struct xhci_td *td) { - u32 field; - /* * Pass all the TRBs to the hardware at once and make sure this write * isn't reordered. */ wmb(); start_trb->field[3] |= start_cycle; - field = xhci_readl(xhci, &xhci->dba->doorbell[slot_id]) & DB_MASK; - xhci_writel(xhci, field | EPI_TO_DB(ep_index), - &xhci->dba->doorbell[slot_id]); - /* Flush PCI posted writes */ - xhci_readl(xhci, &xhci->dba->doorbell[slot_id]); + ring_ep_doorbell(xhci, slot_id, ep_index); } int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags, @@ -1221,3 +1613,36 @@ int queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 s return queue_command(xhci, in_ctx_ptr, 0, 0, TRB_TYPE(TRB_CONFIG_EP) | SLOT_ID_FOR_TRB(slot_id)); } + +int queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index) +{ + u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); + u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); + u32 type = TRB_TYPE(TRB_STOP_RING); + + return queue_command(xhci, 0, 0, 0, + trb_slot_id | trb_ep_index | type); +} + +/* Set Transfer Ring Dequeue Pointer command. + * This should not be used for endpoints that have streams enabled. + */ +static int queue_set_tr_deq(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index, struct xhci_segment *deq_seg, + union xhci_trb *deq_ptr, u32 cycle_state) +{ + dma_addr_t addr; + u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); + u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); + u32 type = TRB_TYPE(TRB_SET_DEQ); + + addr = trb_virt_to_dma(deq_seg, deq_ptr); + if (addr == 0) + xhci_warn(xhci, "WARN Cannot submit Set TR Deq Ptr\n"); + xhci_warn(xhci, "WARN deq seg = 0x%x, deq pt = 0x%x\n", + (unsigned int) deq_seg, + (unsigned int) deq_ptr); + return queue_command(xhci, (u32) addr | cycle_state, 0, 0, + trb_slot_id | trb_ep_index | type); +} diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 06e07616631f..7b7103405c69 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -514,6 +514,7 @@ struct xhci_slot_ctx { /* bits 8:26 reserved */ /* Slot state */ #define SLOT_STATE (0x1f << 27) +#define GET_SLOT_STATE(p) (((p) & (0x1f << 27)) >> 27) /** @@ -765,6 +766,11 @@ struct xhci_event_cmd { #define TRB_TO_SLOT_ID(p) (((p) & (0xff<<24)) >> 24) #define SLOT_ID_FOR_TRB(p) (((p) & 0xff) << 24) +/* Stop Endpoint TRB - ep_index to endpoint ID for this TRB */ +#define TRB_TO_EP_INDEX(p) ((((p) & (0x1f << 16)) >> 16) - 1) +#define EP_ID_FOR_TRB(p) ((((p) + 1) & 0x1f) << 16) + + /* Port Status Change Event TRB fields */ /* Port ID - bits 31:24 */ #define GET_PORT_ID(p) (((p) & (0xff << 24)) >> 24) @@ -893,12 +899,6 @@ union xhci_trb { #define TRB_MAX_BUFF_SHIFT 16 #define TRB_MAX_BUFF_SIZE (1 << TRB_MAX_BUFF_SHIFT) -struct xhci_td { - struct list_head td_list; - struct urb *urb; - union xhci_trb *last_trb; -}; - struct xhci_segment { union xhci_trb *trbs; /* private to HCD */ @@ -906,6 +906,15 @@ struct xhci_segment { dma_addr_t dma; } __attribute__ ((packed)); +struct xhci_td { + struct list_head td_list; + struct list_head cancelled_td_list; + struct urb *urb; + struct xhci_segment *start_seg; + union xhci_trb *first_trb; + union xhci_trb *last_trb; +}; + struct xhci_ring { struct xhci_segment *first_seg; union xhci_trb *enqueue; @@ -915,6 +924,14 @@ struct xhci_ring { struct xhci_segment *deq_seg; unsigned int deq_updates; struct list_head td_list; + /* ---- Related to URB cancellation ---- */ + struct list_head cancelled_td_list; + unsigned int cancels_pending; + unsigned int state; +#define SET_DEQ_PENDING (1 << 0) + /* The TRB that was last reported in a stopped endpoint ring */ + union xhci_trb *stopped_trb; + struct xhci_td *stopped_td; /* * Write the cycle state into the TRB cycle field to give ownership of * the TRB to the host controller (if we are the producer), or to check @@ -1119,6 +1136,8 @@ void handle_event(struct xhci_hcd *xhci); void set_hc_event_deq(struct xhci_hcd *xhci); int queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id); int queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); +int queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, + unsigned int ep_index); int queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); int queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); -- cgit v1.2.3 From b7258a4aba2b24d5c27a0f6674795e83e7771969 Mon Sep 17 00:00:00 2001 From: Stephen Rothwell <sfr@canb.auug.org.au> Date: Wed, 29 Apr 2009 19:02:47 -0700 Subject: USB: xhci: use xhci_handle_event instead of handle_event The former is way to generic for a global symbol. Fixes this build error: drivers/usb/built-in.o: In function `.handle_event': (.text+0x67dd0): multiple definition of `.handle_event' drivers/pcmcia/built-in.o:(.text+0xcfcc): first defined here drivers/usb/built-in.o: In function `handle_event': (.opd+0x5bc8): multiple definition of `handle_event' drivers/pcmcia/built-in.o:(.opd+0xed0): first defined here Signed-off-by: Stephen Rothwell <sfr@canb.auug.org.au> Cc: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-hcd.c | 2 +- drivers/usb/host/xhci-ring.c | 4 ++-- drivers/usb/host/xhci.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 36e440ce88e5..197e6bb5b93d 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -246,7 +246,7 @@ static void xhci_work(struct xhci_hcd *xhci) xhci_readl(xhci, &xhci->ir_set->irq_pending); /* FIXME this should be a delayed service routine that clears the EHB */ - handle_event(xhci); + xhci_handle_event(xhci); /* Clear the event handler busy flag; the event ring should be empty. */ temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[0]); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index f967a6df83c7..1feca20612d1 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1009,7 +1009,7 @@ cleanup: * This function handles all OS-owned events on the event ring. It may drop * xhci->lock between event processing (e.g. to pass up port status changes). */ -void handle_event(struct xhci_hcd *xhci) +void xhci_handle_event(struct xhci_hcd *xhci) { union xhci_trb *event; int update_ptrs = 1; @@ -1054,7 +1054,7 @@ void handle_event(struct xhci_hcd *xhci) set_hc_event_deq(xhci); } /* Are there more items on the event ring? */ - handle_event(xhci); + xhci_handle_event(xhci); } /**** Endpoint Ring Operations ****/ diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 7b7103405c69..e57a609e381d 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1132,7 +1132,7 @@ void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); dma_addr_t trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); void ring_cmd_db(struct xhci_hcd *xhci); void *setup_one_noop(struct xhci_hcd *xhci); -void handle_event(struct xhci_hcd *xhci); +void xhci_handle_event(struct xhci_hcd *xhci); void set_hc_event_deq(struct xhci_hcd *xhci); int queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id); int queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); -- cgit v1.2.3 From 700e2052c6814b1b1d2714225d568c5c64bc49ae Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Wed, 29 Apr 2009 19:14:08 -0700 Subject: USB: xhci: fix lots of compiler warnings. Turns out someone never built this code on a 64bit platform. Someone owes me a beer... Reported-by: Stephen Rothwell <sfr@canb.auug.org.au> Cc: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-dbg.c | 180 +++++++++++++++++++++---------------------- drivers/usb/host/xhci-hcd.c | 37 ++++----- drivers/usb/host/xhci-mem.c | 64 +++++++-------- drivers/usb/host/xhci-ring.c | 72 +++++++++-------- 4 files changed, 172 insertions(+), 181 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 16ef42a0fe85..264c38059d4a 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -30,12 +30,11 @@ void xhci_dbg_regs(struct xhci_hcd *xhci) { u32 temp; - xhci_dbg(xhci, "// xHCI capability registers at 0x%x:\n", - (unsigned int) xhci->cap_regs); + xhci_dbg(xhci, "// xHCI capability registers at %p:\n", + xhci->cap_regs); temp = xhci_readl(xhci, &xhci->cap_regs->hc_capbase); - xhci_dbg(xhci, "// @%x = 0x%x (CAPLENGTH AND HCIVERSION)\n", - (unsigned int) &xhci->cap_regs->hc_capbase, - (unsigned int) temp); + xhci_dbg(xhci, "// @%p = 0x%x (CAPLENGTH AND HCIVERSION)\n", + &xhci->cap_regs->hc_capbase, temp); xhci_dbg(xhci, "// CAPLENGTH: 0x%x\n", (unsigned int) HC_LENGTH(temp)); #if 0 @@ -43,29 +42,24 @@ void xhci_dbg_regs(struct xhci_hcd *xhci) (unsigned int) HC_VERSION(temp)); #endif - xhci_dbg(xhci, "// xHCI operational registers at 0x%x:\n", - (unsigned int) xhci->op_regs); + xhci_dbg(xhci, "// xHCI operational registers at %p:\n", xhci->op_regs); temp = xhci_readl(xhci, &xhci->cap_regs->run_regs_off); - xhci_dbg(xhci, "// @%x = 0x%x RTSOFF\n", - (unsigned int) &xhci->cap_regs->run_regs_off, + xhci_dbg(xhci, "// @%p = 0x%x RTSOFF\n", + &xhci->cap_regs->run_regs_off, (unsigned int) temp & RTSOFF_MASK); - xhci_dbg(xhci, "// xHCI runtime registers at 0x%x:\n", - (unsigned int) xhci->run_regs); + xhci_dbg(xhci, "// xHCI runtime registers at %p:\n", xhci->run_regs); temp = xhci_readl(xhci, &xhci->cap_regs->db_off); - xhci_dbg(xhci, "// @%x = 0x%x DBOFF\n", - (unsigned int) &xhci->cap_regs->db_off, temp); - xhci_dbg(xhci, "// Doorbell array at 0x%x:\n", - (unsigned int) xhci->dba); + xhci_dbg(xhci, "// @%p = 0x%x DBOFF\n", &xhci->cap_regs->db_off, temp); + xhci_dbg(xhci, "// Doorbell array at %p:\n", xhci->dba); } void xhci_print_cap_regs(struct xhci_hcd *xhci) { u32 temp; - xhci_dbg(xhci, "xHCI capability registers at 0x%x:\n", - (unsigned int) xhci->cap_regs); + xhci_dbg(xhci, "xHCI capability registers at %p:\n", xhci->cap_regs); temp = xhci_readl(xhci, &xhci->cap_regs->hc_capbase); xhci_dbg(xhci, "CAPLENGTH AND HCIVERSION 0x%x:\n", @@ -146,8 +140,7 @@ void xhci_print_status(struct xhci_hcd *xhci) void xhci_print_op_regs(struct xhci_hcd *xhci) { - xhci_dbg(xhci, "xHCI operational registers at 0x%x:\n", - (unsigned int) xhci->op_regs); + xhci_dbg(xhci, "xHCI operational registers at %p:\n", xhci->op_regs); xhci_print_command_reg(xhci); xhci_print_status(xhci); } @@ -168,9 +161,8 @@ void xhci_print_ports(struct xhci_hcd *xhci) addr = &xhci->op_regs->port_status_base; for (i = 0; i < ports; i++) { for (j = 0; j < NUM_PORT_REGS; ++j) { - xhci_dbg(xhci, "0x%x port %s reg = 0x%x\n", - (unsigned int) addr, - names[j], + xhci_dbg(xhci, "%p port %s reg = 0x%x\n", + addr, names[j], (unsigned int) xhci_readl(xhci, addr)); addr++; } @@ -187,46 +179,46 @@ void xhci_print_ir_set(struct xhci_hcd *xhci, struct intr_reg *ir_set, int set_n if (temp == XHCI_INIT_VALUE) return; - xhci_dbg(xhci, " 0x%x: ir_set[%i]\n", (unsigned int) ir_set, set_num); + xhci_dbg(xhci, " %p: ir_set[%i]\n", ir_set, set_num); - xhci_dbg(xhci, " 0x%x: ir_set.pending = 0x%x\n", - (unsigned int) addr, (unsigned int) temp); + xhci_dbg(xhci, " %p: ir_set.pending = 0x%x\n", addr, + (unsigned int)temp); addr = &ir_set->irq_control; temp = xhci_readl(xhci, addr); - xhci_dbg(xhci, " 0x%x: ir_set.control = 0x%x\n", - (unsigned int) addr, (unsigned int) temp); + xhci_dbg(xhci, " %p: ir_set.control = 0x%x\n", addr, + (unsigned int)temp); addr = &ir_set->erst_size; temp = xhci_readl(xhci, addr); - xhci_dbg(xhci, " 0x%x: ir_set.erst_size = 0x%x\n", - (unsigned int) addr, (unsigned int) temp); + xhci_dbg(xhci, " %p: ir_set.erst_size = 0x%x\n", addr, + (unsigned int)temp); addr = &ir_set->rsvd; temp = xhci_readl(xhci, addr); if (temp != XHCI_INIT_VALUE) - xhci_dbg(xhci, " WARN: 0x%x: ir_set.rsvd = 0x%x\n", - (unsigned int) addr, (unsigned int) temp); + xhci_dbg(xhci, " WARN: %p: ir_set.rsvd = 0x%x\n", + addr, (unsigned int)temp); addr = &ir_set->erst_base[0]; temp = xhci_readl(xhci, addr); - xhci_dbg(xhci, " 0x%x: ir_set.erst_base[0] = 0x%x\n", - (unsigned int) addr, (unsigned int) temp); + xhci_dbg(xhci, " %p: ir_set.erst_base[0] = 0x%x\n", + addr, (unsigned int) temp); addr = &ir_set->erst_base[1]; temp = xhci_readl(xhci, addr); - xhci_dbg(xhci, " 0x%x: ir_set.erst_base[1] = 0x%x\n", - (unsigned int) addr, (unsigned int) temp); + xhci_dbg(xhci, " %p: ir_set.erst_base[1] = 0x%x\n", + addr, (unsigned int) temp); addr = &ir_set->erst_dequeue[0]; temp = xhci_readl(xhci, addr); - xhci_dbg(xhci, " 0x%x: ir_set.erst_dequeue[0] = 0x%x\n", - (unsigned int) addr, (unsigned int) temp); + xhci_dbg(xhci, " %p: ir_set.erst_dequeue[0] = 0x%x\n", + addr, (unsigned int) temp); addr = &ir_set->erst_dequeue[1]; temp = xhci_readl(xhci, addr); - xhci_dbg(xhci, " 0x%x: ir_set.erst_dequeue[1] = 0x%x\n", - (unsigned int) addr, (unsigned int) temp); + xhci_dbg(xhci, " %p: ir_set.erst_dequeue[1] = 0x%x\n", + addr, (unsigned int) temp); } void xhci_print_run_regs(struct xhci_hcd *xhci) @@ -234,17 +226,16 @@ void xhci_print_run_regs(struct xhci_hcd *xhci) u32 temp; int i; - xhci_dbg(xhci, "xHCI runtime registers at 0x%x:\n", - (unsigned int) xhci->run_regs); + xhci_dbg(xhci, "xHCI runtime registers at %p:\n", xhci->run_regs); temp = xhci_readl(xhci, &xhci->run_regs->microframe_index); - xhci_dbg(xhci, " 0x%x: Microframe index = 0x%x\n", - (unsigned int) &xhci->run_regs->microframe_index, + xhci_dbg(xhci, " %p: Microframe index = 0x%x\n", + &xhci->run_regs->microframe_index, (unsigned int) temp); for (i = 0; i < 7; ++i) { temp = xhci_readl(xhci, &xhci->run_regs->rsvd[i]); if (temp != XHCI_INIT_VALUE) - xhci_dbg(xhci, " WARN: 0x%x: Rsvd[%i] = 0x%x\n", - (unsigned int) &xhci->run_regs->rsvd[i], + xhci_dbg(xhci, " WARN: %p: Rsvd[%i] = 0x%x\n", + &xhci->run_regs->rsvd[i], i, (unsigned int) temp); } } @@ -347,14 +338,16 @@ void xhci_debug_segment(struct xhci_hcd *xhci, struct xhci_segment *seg) void xhci_dbg_ring_ptrs(struct xhci_hcd *xhci, struct xhci_ring *ring) { - xhci_dbg(xhci, "Ring deq = 0x%x (virt), 0x%x (dma)\n", - (unsigned int) ring->dequeue, - trb_virt_to_dma(ring->deq_seg, ring->dequeue)); + xhci_dbg(xhci, "Ring deq = %p (virt), 0x%llx (dma)\n", + ring->dequeue, + (unsigned long long)trb_virt_to_dma(ring->deq_seg, + ring->dequeue)); xhci_dbg(xhci, "Ring deq updated %u times\n", ring->deq_updates); - xhci_dbg(xhci, "Ring enq = 0x%x (virt), 0x%x (dma)\n", - (unsigned int) ring->enqueue, - trb_virt_to_dma(ring->enq_seg, ring->enqueue)); + xhci_dbg(xhci, "Ring enq = %p (virt), 0x%llx (dma)\n", + ring->enqueue, + (unsigned long long)trb_virt_to_dma(ring->enq_seg, + ring->enqueue)); xhci_dbg(xhci, "Ring enq updated %u times\n", ring->enq_updates); } @@ -418,42 +411,42 @@ void xhci_dbg_ctx(struct xhci_hcd *xhci, struct xhci_device_control *ctx, dma_ad /* Fields are 32 bits wide, DMA addresses are in bytes */ int field_size = 32 / 8; - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - drop flags\n", - (unsigned int) &ctx->drop_flags, - dma, ctx->drop_flags); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - drop flags\n", + &ctx->drop_flags, (unsigned long long)dma, + ctx->drop_flags); dma += field_size; - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - add flags\n", - (unsigned int) &ctx->add_flags, - dma, ctx->add_flags); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - add flags\n", + &ctx->add_flags, (unsigned long long)dma, + ctx->add_flags); dma += field_size; for (i = 0; i > 6; ++i) { - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - rsvd[%d]\n", - (unsigned int) &ctx->rsvd[i], - dma, ctx->rsvd[i], i); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - rsvd[%d]\n", + &ctx->rsvd[i], (unsigned long long)dma, + ctx->rsvd[i], i); dma += field_size; } xhci_dbg(xhci, "Slot Context:\n"); - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - dev_info\n", - (unsigned int) &ctx->slot.dev_info, - dma, ctx->slot.dev_info); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - dev_info\n", + &ctx->slot.dev_info, + (unsigned long long)dma, ctx->slot.dev_info); dma += field_size; - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - dev_info2\n", - (unsigned int) &ctx->slot.dev_info2, - dma, ctx->slot.dev_info2); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - dev_info2\n", + &ctx->slot.dev_info2, + (unsigned long long)dma, ctx->slot.dev_info2); dma += field_size; - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - tt_info\n", - (unsigned int) &ctx->slot.tt_info, - dma, ctx->slot.tt_info); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - tt_info\n", + &ctx->slot.tt_info, + (unsigned long long)dma, ctx->slot.tt_info); dma += field_size; - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - dev_state\n", - (unsigned int) &ctx->slot.dev_state, - dma, ctx->slot.dev_state); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - dev_state\n", + &ctx->slot.dev_state, + (unsigned long long)dma, ctx->slot.dev_state); dma += field_size; for (i = 0; i > 4; ++i) { - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - rsvd[%d]\n", - (unsigned int) &ctx->slot.reserved[i], - dma, ctx->slot.reserved[i], i); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - rsvd[%d]\n", + &ctx->slot.reserved[i], (unsigned long long)dma, + ctx->slot.reserved[i], i); dma += field_size; } @@ -461,30 +454,31 @@ void xhci_dbg_ctx(struct xhci_hcd *xhci, struct xhci_device_control *ctx, dma_ad last_ep_ctx = last_ep + 1; for (i = 0; i < last_ep_ctx; ++i) { xhci_dbg(xhci, "Endpoint %02d Context:\n", i); - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - ep_info\n", - (unsigned int) &ctx->ep[i].ep_info, - dma, ctx->ep[i].ep_info); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - ep_info\n", + &ctx->ep[i].ep_info, + (unsigned long long)dma, ctx->ep[i].ep_info); dma += field_size; - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - ep_info2\n", - (unsigned int) &ctx->ep[i].ep_info2, - dma, ctx->ep[i].ep_info2); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - ep_info2\n", + &ctx->ep[i].ep_info2, + (unsigned long long)dma, ctx->ep[i].ep_info2); dma += field_size; - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - deq[0]\n", - (unsigned int) &ctx->ep[i].deq[0], - dma, ctx->ep[i].deq[0]); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - deq[0]\n", + &ctx->ep[i].deq[0], + (unsigned long long)dma, ctx->ep[i].deq[0]); dma += field_size; - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - deq[1]\n", - (unsigned int) &ctx->ep[i].deq[1], - dma, ctx->ep[i].deq[1]); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - deq[1]\n", + &ctx->ep[i].deq[1], + (unsigned long long)dma, ctx->ep[i].deq[1]); dma += field_size; - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - tx_info\n", - (unsigned int) &ctx->ep[i].tx_info, - dma, ctx->ep[i].tx_info); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - tx_info\n", + &ctx->ep[i].tx_info, + (unsigned long long)dma, ctx->ep[i].tx_info); dma += field_size; for (j = 0; j < 3; ++j) { - xhci_dbg(xhci, "@%08x (virt) @%08x (dma) %#08x - rsvd[%d]\n", - (unsigned int) &ctx->ep[i].reserved[j], - dma, ctx->ep[i].reserved[j], j); + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - rsvd[%d]\n", + &ctx->ep[i].reserved[j], + (unsigned long long)dma, + ctx->ep[i].reserved[j], j); dma += field_size; } } diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 197e6bb5b93d..9ffa1fa507cf 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -397,10 +397,8 @@ int xhci_run(struct usb_hcd *hcd) xhci_writel(xhci, temp, &xhci->op_regs->command); temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); - xhci_dbg(xhci, "// Enabling event ring interrupter 0x%x" - " by writing 0x%x to irq_pending\n", - (unsigned int) xhci->ir_set, - (unsigned int) ER_IRQ_ENABLE(temp)); + xhci_dbg(xhci, "// Enabling event ring interrupter %p by writing 0x%x to irq_pending\n", + xhci->ir_set, (unsigned int) ER_IRQ_ENABLE(temp)); xhci_writel(xhci, ER_IRQ_ENABLE(temp), &xhci->ir_set->irq_pending); xhci_print_ir_set(xhci, xhci->ir_set, 0); @@ -431,8 +429,7 @@ int xhci_run(struct usb_hcd *hcd) xhci_writel(xhci, temp, &xhci->op_regs->command); /* Flush PCI posted writes */ temp = xhci_readl(xhci, &xhci->op_regs->command); - xhci_dbg(xhci, "// @%x = 0x%x\n", - (unsigned int) &xhci->op_regs->command, temp); + xhci_dbg(xhci, "// @%p = 0x%x\n", &xhci->op_regs->command, temp); if (doorbell) (*doorbell)(xhci); @@ -660,7 +657,7 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) if (ret || !urb->hcpriv) goto done; - xhci_dbg(xhci, "Cancel URB 0x%x\n", (unsigned int) urb); + xhci_dbg(xhci, "Cancel URB %p\n", urb); ep_index = xhci_get_endpoint_index(&urb->ep->desc); ep_ring = xhci->devs[urb->dev->slot_id]->ep_rings[ep_index]; td = (struct xhci_td *) urb->hcpriv; @@ -702,10 +699,10 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, int ret; ret = xhci_check_args(hcd, udev, ep, 1, __func__); - xhci_dbg(xhci, "%s called for udev %#x\n", __func__, (unsigned int) udev); if (ret <= 0) return ret; xhci = hcd_to_xhci(hcd); + xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); drop_flag = xhci_get_endpoint_flag(&ep->desc); if (drop_flag == SLOT_FLAG || drop_flag == EP0_FLAG) { @@ -730,8 +727,8 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, */ if ((ep_ctx->ep_info & EP_STATE_MASK) == EP_STATE_DISABLED || in_ctx->drop_flags & xhci_get_endpoint_flag(&ep->desc)) { - xhci_warn(xhci, "xHCI %s called with disabled ep %#x\n", - __func__, (unsigned int) ep); + xhci_warn(xhci, "xHCI %s called with disabled ep %p\n", + __func__, ep); spin_unlock_irqrestore(&xhci->lock, flags); return 0; } @@ -817,8 +814,8 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, * ignore this request. */ if (in_ctx->add_flags & xhci_get_endpoint_flag(&ep->desc)) { - xhci_warn(xhci, "xHCI %s called with enabled ep %#x\n", - __func__, (unsigned int) ep); + xhci_warn(xhci, "xHCI %s called with enabled ep %p\n", + __func__, ep); spin_unlock_irqrestore(&xhci->lock, flags); return 0; } @@ -904,7 +901,7 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) spin_unlock_irqrestore(&xhci->lock, flags); return -EINVAL; } - xhci_dbg(xhci, "%s called for udev %#x\n", __func__, (unsigned int) udev); + xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); virt_dev = xhci->devs[udev->slot_id]; /* See section 4.6.6 - A0 = 1; A1 = D0 = D1 = 0 */ @@ -1009,7 +1006,7 @@ void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) spin_unlock_irqrestore(&xhci->lock, flags); return; } - xhci_dbg(xhci, "%s called for udev %#x\n", __func__, (unsigned int) udev); + xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); virt_dev = xhci->devs[udev->slot_id]; /* Free any rings allocated for added endpoints */ for (i = 0; i < 31; ++i) { @@ -1184,16 +1181,16 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) xhci_dbg(xhci, "Op regs DCBAA ptr[0] = %#08x\n", temp); temp = xhci_readl(xhci, &xhci->op_regs->dcbaa_ptr[1]); xhci_dbg(xhci, "Op regs DCBAA ptr[1] = %#08x\n", temp); - xhci_dbg(xhci, "Slot ID %d dcbaa entry[0] @%08x = %#08x\n", + xhci_dbg(xhci, "Slot ID %d dcbaa entry[0] @%p = %#08x\n", udev->slot_id, - (unsigned int) &xhci->dcbaa->dev_context_ptrs[2*udev->slot_id], + &xhci->dcbaa->dev_context_ptrs[2*udev->slot_id], xhci->dcbaa->dev_context_ptrs[2*udev->slot_id]); - xhci_dbg(xhci, "Slot ID %d dcbaa entry[1] @%08x = %#08x\n", + xhci_dbg(xhci, "Slot ID %d dcbaa entry[1] @%p = %#08x\n", udev->slot_id, - (unsigned int) &xhci->dcbaa->dev_context_ptrs[2*udev->slot_id+1], + &xhci->dcbaa->dev_context_ptrs[2*udev->slot_id+1], xhci->dcbaa->dev_context_ptrs[2*udev->slot_id+1]); - xhci_dbg(xhci, "Output Context DMA address = %#08x\n", - virt_dev->out_ctx_dma); + xhci_dbg(xhci, "Output Context DMA address = %#08llx\n", + (unsigned long long)virt_dev->out_ctx_dma); xhci_dbg(xhci, "Slot ID %d Input Context:\n", udev->slot_id); xhci_dbg_ctx(xhci, virt_dev->in_ctx, virt_dev->in_ctx_dma, 2); xhci_dbg(xhci, "Slot ID %d Output Context:\n", udev->slot_id); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index e81d10a653ef..6b75ca9180e3 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -40,16 +40,15 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, gfp_t flag seg = kzalloc(sizeof *seg, flags); if (!seg) return 0; - xhci_dbg(xhci, "Allocating priv segment structure at 0x%x\n", - (unsigned int) seg); + xhci_dbg(xhci, "Allocating priv segment structure at %p\n", seg); seg->trbs = dma_pool_alloc(xhci->segment_pool, flags, &dma); if (!seg->trbs) { kfree(seg); return 0; } - xhci_dbg(xhci, "// Allocating segment at 0x%x (virtual) 0x%x (DMA)\n", - (unsigned int) seg->trbs, (u32) dma); + xhci_dbg(xhci, "// Allocating segment at %p (virtual) 0x%llx (DMA)\n", + seg->trbs, (unsigned long long)dma); memset(seg->trbs, 0, SEGMENT_SIZE); seg->dma = dma; @@ -63,14 +62,12 @@ static void xhci_segment_free(struct xhci_hcd *xhci, struct xhci_segment *seg) if (!seg) return; if (seg->trbs) { - xhci_dbg(xhci, "Freeing DMA segment at 0x%x" - " (virtual) 0x%x (DMA)\n", - (unsigned int) seg->trbs, (u32) seg->dma); + xhci_dbg(xhci, "Freeing DMA segment at %p (virtual) 0x%llx (DMA)\n", + seg->trbs, (unsigned long long)seg->dma); dma_pool_free(xhci->segment_pool, seg->trbs, seg->dma); seg->trbs = NULL; } - xhci_dbg(xhci, "Freeing priv segment structure at 0x%x\n", - (unsigned int) seg); + xhci_dbg(xhci, "Freeing priv segment structure at %p\n", seg); kfree(seg); } @@ -98,8 +95,9 @@ static void xhci_link_segments(struct xhci_hcd *xhci, struct xhci_segment *prev, val |= TRB_TYPE(TRB_LINK); prev->trbs[TRBS_PER_SEGMENT-1].link.control = val; } - xhci_dbg(xhci, "Linking segment 0x%x to segment 0x%x (DMA)\n", - prev->dma, next->dma); + xhci_dbg(xhci, "Linking segment 0x%llx to segment 0x%llx (DMA)\n", + (unsigned long long)prev->dma, + (unsigned long long)next->dma); } /* XXX: Do we need the hcd structure in all these functions? */ @@ -112,7 +110,7 @@ void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring) return; first_seg = ring->first_seg; seg = first_seg->next; - xhci_dbg(xhci, "Freeing ring at 0x%x\n", (unsigned int) ring); + xhci_dbg(xhci, "Freeing ring at %p\n", ring); while (seg != first_seg) { struct xhci_segment *next = seg->next; xhci_segment_free(xhci, seg); @@ -137,7 +135,7 @@ static struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, struct xhci_segment *prev; ring = kzalloc(sizeof *(ring), flags); - xhci_dbg(xhci, "Allocating ring at 0x%x\n", (unsigned int) ring); + xhci_dbg(xhci, "Allocating ring at %p\n", ring); if (!ring) return 0; @@ -169,8 +167,8 @@ static struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, /* See section 4.9.2.1 and 6.4.4.1 */ prev->trbs[TRBS_PER_SEGMENT-1].link.control |= (LINK_TOGGLE); xhci_dbg(xhci, "Wrote link toggle flag to" - " segment 0x%x (virtual), 0x%x (DMA)\n", - (unsigned int) prev, (u32) prev->dma); + " segment %p (virtual), 0x%llx (DMA)\n", + prev, (unsigned long long)prev->dma); } /* The ring is empty, so the enqueue pointer == dequeue pointer */ ring->enqueue = ring->first_seg->trbs; @@ -242,7 +240,8 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, if (!dev->out_ctx) goto fail; dev->out_ctx_dma = dma; - xhci_dbg(xhci, "Slot %d output ctx = 0x%x (dma)\n", slot_id, dma); + xhci_dbg(xhci, "Slot %d output ctx = 0x%llx (dma)\n", slot_id, + (unsigned long long)dma); memset(dev->out_ctx, 0, sizeof(*dev->out_ctx)); /* Allocate the (input) device context for address device command */ @@ -250,7 +249,8 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, if (!dev->in_ctx) goto fail; dev->in_ctx_dma = dma; - xhci_dbg(xhci, "Slot %d input ctx = 0x%x (dma)\n", slot_id, dma); + xhci_dbg(xhci, "Slot %d input ctx = 0x%llx (dma)\n", slot_id, + (unsigned long long)dma); memset(dev->in_ctx, 0, sizeof(*dev->in_ctx)); /* Allocate endpoint 0 ring */ @@ -266,10 +266,10 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, */ xhci->dcbaa->dev_context_ptrs[2*slot_id] = (u32) dev->out_ctx_dma + (32); - xhci_dbg(xhci, "Set slot id %d dcbaa entry 0x%x to 0x%x\n", + xhci_dbg(xhci, "Set slot id %d dcbaa entry %p to 0x%llx\n", slot_id, - (unsigned int) &xhci->dcbaa->dev_context_ptrs[2*slot_id], - dev->out_ctx_dma); + &xhci->dcbaa->dev_context_ptrs[2*slot_id], + (unsigned long long)dev->out_ctx_dma); xhci->dcbaa->dev_context_ptrs[2*slot_id + 1] = 0; return 1; @@ -339,7 +339,7 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud dev->in_ctx->slot.tt_info = udev->tt->hub->slot_id; dev->in_ctx->slot.tt_info |= udev->ttport << 8; } - xhci_dbg(xhci, "udev->tt = 0x%x\n", (unsigned int) udev->tt); + xhci_dbg(xhci, "udev->tt = %p\n", udev->tt); xhci_dbg(xhci, "udev->ttport = 0x%x\n", udev->ttport); /* Step 4 - ring already allocated */ @@ -643,8 +643,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) goto fail; memset(xhci->dcbaa, 0, sizeof *(xhci->dcbaa)); xhci->dcbaa->dma = dma; - xhci_dbg(xhci, "// Device context base array address = 0x%x (DMA), 0x%x (virt)\n", - xhci->dcbaa->dma, (unsigned int) xhci->dcbaa); + xhci_dbg(xhci, "// Device context base array address = 0x%llx (DMA), %p (virt)\n", + (unsigned long long)xhci->dcbaa->dma, xhci->dcbaa); xhci_writel(xhci, (u32) 0, &xhci->op_regs->dcbaa_ptr[1]); xhci_writel(xhci, dma, &xhci->op_regs->dcbaa_ptr[0]); @@ -668,8 +668,9 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci->cmd_ring = xhci_ring_alloc(xhci, 1, true, flags); if (!xhci->cmd_ring) goto fail; - xhci_dbg(xhci, "Allocated command ring at 0x%x\n", (unsigned int) xhci->cmd_ring); - xhci_dbg(xhci, "First segment DMA is 0x%x\n", (unsigned int) xhci->cmd_ring->first_seg->dma); + xhci_dbg(xhci, "Allocated command ring at %p\n", xhci->cmd_ring); + xhci_dbg(xhci, "First segment DMA is 0x%llx\n", + (unsigned long long)xhci->cmd_ring->first_seg->dma); /* Set the address in the Command Ring Control register */ val = xhci_readl(xhci, &xhci->op_regs->cmd_ring[0]); @@ -705,15 +706,16 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) sizeof(struct xhci_erst_entry)*ERST_NUM_SEGS, &dma); if (!xhci->erst.entries) goto fail; - xhci_dbg(xhci, "// Allocated event ring segment table at 0x%x\n", dma); + xhci_dbg(xhci, "// Allocated event ring segment table at 0x%llx\n", + (unsigned long long)dma); memset(xhci->erst.entries, 0, sizeof(struct xhci_erst_entry)*ERST_NUM_SEGS); xhci->erst.num_entries = ERST_NUM_SEGS; xhci->erst.erst_dma_addr = dma; - xhci_dbg(xhci, "Set ERST to 0; private num segs = %i, virt addr = 0x%x, dma addr = 0x%x\n", + xhci_dbg(xhci, "Set ERST to 0; private num segs = %i, virt addr = %p, dma addr = 0x%llx\n", xhci->erst.num_entries, - (unsigned int) xhci->erst.entries, - xhci->erst.erst_dma_addr); + xhci->erst.entries, + (unsigned long long)xhci->erst.erst_dma_addr); /* set ring base address and size for each segment table entry */ for (val = 0, seg = xhci->event_ring->first_seg; val < ERST_NUM_SEGS; val++) { @@ -735,8 +737,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci_dbg(xhci, "// Set ERST entries to point to event ring.\n"); /* set the segment table base address */ - xhci_dbg(xhci, "// Set ERST base address for ir_set 0 = 0x%x\n", - xhci->erst.erst_dma_addr); + xhci_dbg(xhci, "// Set ERST base address for ir_set 0 = 0x%llx\n", + (unsigned long long)xhci->erst.erst_dma_addr); xhci_writel(xhci, 0, &xhci->ir_set->erst_base[1]); val = xhci_readl(xhci, &xhci->ir_set->erst_base[0]); val &= ERST_PTR_MASK; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1feca20612d1..9d6874710669 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -74,12 +74,12 @@ dma_addr_t trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb) { - unsigned int offset; + dma_addr_t offset; if (!seg || !trb || (void *) trb < (void *) seg->trbs) return 0; /* offset in bytes, since these are byte-addressable */ - offset = (unsigned int) trb - (unsigned int) seg->trbs; + offset = trb - seg->trbs; /* SEGMENT_SIZE in bytes, trbs are 16-byte aligned */ if (offset > SEGMENT_SIZE || (offset % sizeof(*trb)) != 0) return 0; @@ -145,8 +145,8 @@ static void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring, bool consumer if (consumer && last_trb_on_last_seg(xhci, ring, ring->deq_seg, next)) { ring->cycle_state = (ring->cycle_state ? 0 : 1); if (!in_interrupt()) - xhci_dbg(xhci, "Toggle cycle state for ring 0x%x = %i\n", - (unsigned int) ring, + xhci_dbg(xhci, "Toggle cycle state for ring %p = %i\n", + ring, (unsigned int) ring->cycle_state); } ring->deq_seg = ring->deq_seg->next; @@ -195,8 +195,8 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, bool consumer if (last_trb_on_last_seg(xhci, ring, ring->enq_seg, next)) { ring->cycle_state = (ring->cycle_state ? 0 : 1); if (!in_interrupt()) - xhci_dbg(xhci, "Toggle cycle state for ring 0x%x = %i\n", - (unsigned int) ring, + xhci_dbg(xhci, "Toggle cycle state for ring %p = %i\n", + ring, (unsigned int) ring->cycle_state); } } @@ -387,12 +387,12 @@ void td_to_noop(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, */ cur_trb->generic.field[3] &= ~TRB_CHAIN; xhci_dbg(xhci, "Cancel (unchain) link TRB\n"); - xhci_dbg(xhci, "Address = 0x%x (0x%x dma); " - "in seg 0x%x (0x%x dma)\n", - (unsigned int) cur_trb, - trb_virt_to_dma(cur_seg, cur_trb), - (unsigned int) cur_seg, - cur_seg->dma); + xhci_dbg(xhci, "Address = %p (0x%llx dma); " + "in seg %p (0x%llx dma)\n", + cur_trb, + (unsigned long long)trb_virt_to_dma(cur_seg, cur_trb), + cur_seg, + (unsigned long long)cur_seg->dma); } else { cur_trb->generic.field[0] = 0; cur_trb->generic.field[1] = 0; @@ -400,12 +400,12 @@ void td_to_noop(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, /* Preserve only the cycle bit of this TRB */ cur_trb->generic.field[3] &= TRB_CYCLE; cur_trb->generic.field[3] |= TRB_TYPE(TRB_TR_NOOP); - xhci_dbg(xhci, "Cancel TRB 0x%x (0x%x dma) " - "in seg 0x%x (0x%x dma)\n", - (unsigned int) cur_trb, - trb_virt_to_dma(cur_seg, cur_trb), - (unsigned int) cur_seg, - cur_seg->dma); + xhci_dbg(xhci, "Cancel TRB %p (0x%llx dma) " + "in seg %p (0x%llx dma)\n", + cur_trb, + (unsigned long long)trb_virt_to_dma(cur_seg, cur_trb), + cur_seg, + (unsigned long long)cur_seg->dma); } if (cur_trb == cur_td->last_trb) break; @@ -456,9 +456,9 @@ static void handle_stopped_endpoint(struct xhci_hcd *xhci, */ list_for_each(entry, &ep_ring->cancelled_td_list) { cur_td = list_entry(entry, struct xhci_td, cancelled_td_list); - xhci_dbg(xhci, "Cancelling TD starting at 0x%x, 0x%x (dma).\n", - (unsigned int) cur_td->first_trb, - trb_virt_to_dma(cur_td->start_seg, cur_td->first_trb)); + xhci_dbg(xhci, "Cancelling TD starting at %p, 0x%llx (dma).\n", + cur_td->first_trb, + (unsigned long long)trb_virt_to_dma(cur_td->start_seg, cur_td->first_trb)); /* * If we stopped on the TD we need to cancel, then we have to * move the xHC endpoint ring dequeue pointer past this TD. @@ -480,12 +480,12 @@ static void handle_stopped_endpoint(struct xhci_hcd *xhci, /* If necessary, queue a Set Transfer Ring Dequeue Pointer command */ if (deq_state.new_deq_ptr && deq_state.new_deq_seg) { - xhci_dbg(xhci, "Set TR Deq Ptr cmd, new deq seg = 0x%x (0x%x dma), " - "new deq ptr = 0x%x (0x%x dma), new cycle = %u\n", - (unsigned int) deq_state.new_deq_seg, - deq_state.new_deq_seg->dma, - (unsigned int) deq_state.new_deq_ptr, - trb_virt_to_dma(deq_state.new_deq_seg, deq_state.new_deq_ptr), + xhci_dbg(xhci, "Set TR Deq Ptr cmd, new deq seg = %p (0x%llx dma), " + "new deq ptr = %p (0x%llx dma), new cycle = %u\n", + deq_state.new_deq_seg, + (unsigned long long)deq_state.new_deq_seg->dma, + deq_state.new_deq_ptr, + (unsigned long long)trb_virt_to_dma(deq_state.new_deq_seg, deq_state.new_deq_ptr), deq_state.new_cycle_state); queue_set_tr_deq(xhci, slot_id, ep_index, deq_state.new_deq_seg, @@ -522,8 +522,7 @@ static void handle_stopped_endpoint(struct xhci_hcd *xhci, cur_td->urb->hcpriv = NULL; usb_hcd_unlink_urb_from_ep(xhci_to_hcd(xhci), cur_td->urb); - xhci_dbg(xhci, "Giveback cancelled URB 0x%x\n", - (unsigned int) cur_td->urb); + xhci_dbg(xhci, "Giveback cancelled URB %p\n", cur_td->urb); spin_unlock(&xhci->lock); /* Doesn't matter what we pass for status, since the core will * just overwrite it (because the URB has been unlinked). @@ -1183,9 +1182,9 @@ unsigned int count_sg_trbs_needed(struct xhci_hcd *xhci, struct urb *urb) num_trbs++; running_total += TRB_MAX_BUFF_SIZE; } - xhci_dbg(xhci, " sg #%d: dma = %#x, len = %#x (%d), num_trbs = %d\n", - i, sg_dma_address(sg), len, len, - num_trbs - previous_total_trbs); + xhci_dbg(xhci, " sg #%d: dma = %#llx, len = %#x (%d), num_trbs = %d\n", + i, (unsigned long long)sg_dma_address(sg), + len, len, num_trbs - previous_total_trbs); len = min_t(int, len, temp); temp -= len; @@ -1394,11 +1393,11 @@ int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, /* FIXME: this doesn't deal with URB_ZERO_PACKET - need one more */ if (!in_interrupt()) - dev_dbg(&urb->dev->dev, "ep %#x - urb len = %#x (%d), addr = %#x, num_trbs = %d\n", + dev_dbg(&urb->dev->dev, "ep %#x - urb len = %#x (%d), addr = %#llx, num_trbs = %d\n", urb->ep->desc.bEndpointAddress, urb->transfer_buffer_length, urb->transfer_buffer_length, - urb->transfer_dma, + (unsigned long long)urb->transfer_dma, num_trbs); ret = xhci_prepare_transfer(xhci, xhci->devs[slot_id], ep_index, @@ -1640,9 +1639,8 @@ static int queue_set_tr_deq(struct xhci_hcd *xhci, int slot_id, addr = trb_virt_to_dma(deq_seg, deq_ptr); if (addr == 0) xhci_warn(xhci, "WARN Cannot submit Set TR Deq Ptr\n"); - xhci_warn(xhci, "WARN deq seg = 0x%x, deq pt = 0x%x\n", - (unsigned int) deq_seg, - (unsigned int) deq_ptr); + xhci_warn(xhci, "WARN deq seg = %p, deq pt = %p\n", + deq_seg, deq_ptr); return queue_command(xhci, (u32) addr | cycle_state, 0, 0, trb_slot_id | trb_ep_index | type); } -- cgit v1.2.3 From 045f123d9c83b9a18c9d43a9afbf52bf0799640d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Wed, 29 Apr 2009 19:12:44 -0700 Subject: USB: xhci: fix some compiler warnings in xhci.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the warning: drivers/usb/host/xhci.h:1083: warning: passing argument 1 of ‘xhci_to_hcd’ discards qualifiers from pointer target type drivers/usb/host/xhci.h:1083: warning: passing argument 1 of ‘xhci_to_hcd’ discards qualifiers from pointer target type Reported-by: Stephen Rothwell <sfr@canb.auug.org.au> Cc: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index e57a609e381d..13c9166e758a 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1068,12 +1068,13 @@ static inline unsigned int xhci_readl(const struct xhci_hcd *xhci, { return readl(regs); } -static inline void xhci_writel(const struct xhci_hcd *xhci, +static inline void xhci_writel(struct xhci_hcd *xhci, const unsigned int val, __u32 __iomem *regs) { if (!in_interrupt()) - xhci_dbg(xhci, "`MEM_WRITE_DWORD(3'b000, 32'h%0x, 32'h%0x, 4'hf);\n", - (unsigned int) regs, val); + xhci_dbg(xhci, + "`MEM_WRITE_DWORD(3'b000, 32'h%p, 32'h%0x, 4'hf);\n", + regs, val); writel(val, regs); } -- cgit v1.2.3 From 3841d56ebb9730c786a59bf3207529c35214df26 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Wed, 29 Apr 2009 19:04:32 -0700 Subject: USB: xhci: Fix register write order. The 0.95 xHCI spec says that if the xHCI HW support 64-bit addressing, you must write the whole 64-bit address as one atomic operation, or write the low 32 bits, and then the high 32 bits. I had the register writes swapped in some places. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-hcd.c | 4 ++-- drivers/usb/host/xhci-mem.c | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 9ffa1fa507cf..13188077387c 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -416,11 +416,11 @@ int xhci_run(struct usb_hcd *hcd) xhci_dbg(xhci, "Event ring:\n"); xhci_debug_ring(xhci, xhci->event_ring); xhci_dbg_ring_ptrs(xhci, xhci->event_ring); - temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[1]); - xhci_dbg(xhci, "ERST deq upper = 0x%x\n", temp); temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[0]); temp &= ERST_PTR_MASK; xhci_dbg(xhci, "ERST deq = 0x%x\n", temp); + temp = xhci_readl(xhci, &xhci->ir_set->erst_dequeue[1]); + xhci_dbg(xhci, "ERST deq upper = 0x%x\n", temp); temp = xhci_readl(xhci, &xhci->op_regs->command); temp |= (CMD_RUN); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 6b75ca9180e3..6523e399fe73 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -475,8 +475,8 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, if (!virt_dev->new_ep_rings[ep_index]) return -ENOMEM; ep_ring = virt_dev->new_ep_rings[ep_index]; - ep_ctx->deq[1] = 0; ep_ctx->deq[0] = ep_ring->first_seg->dma | ep_ring->cycle_state; + ep_ctx->deq[1] = 0; ep_ctx->ep_info = xhci_get_endpoint_interval(udev, ep); @@ -533,8 +533,8 @@ void xhci_endpoint_zero(struct xhci_hcd *xhci, ep_ctx->ep_info = 0; ep_ctx->ep_info2 = 0; - ep_ctx->deq[1] = 0; ep_ctx->deq[0] = 0; + ep_ctx->deq[1] = 0; ep_ctx->tx_info = 0; /* Don't free the endpoint ring until the set interface or configuration * request succeeds. @@ -549,10 +549,10 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) /* Free the Event Ring Segment Table and the actual Event Ring */ xhci_writel(xhci, 0, &xhci->ir_set->erst_size); - xhci_writel(xhci, 0, &xhci->ir_set->erst_base[1]); xhci_writel(xhci, 0, &xhci->ir_set->erst_base[0]); - xhci_writel(xhci, 0, &xhci->ir_set->erst_dequeue[1]); + xhci_writel(xhci, 0, &xhci->ir_set->erst_base[1]); xhci_writel(xhci, 0, &xhci->ir_set->erst_dequeue[0]); + xhci_writel(xhci, 0, &xhci->ir_set->erst_dequeue[1]); size = sizeof(struct xhci_erst_entry)*(xhci->erst.num_entries); if (xhci->erst.entries) pci_free_consistent(pdev, size, @@ -564,8 +564,8 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci->event_ring = NULL; xhci_dbg(xhci, "Freed event ring\n"); - xhci_writel(xhci, 0, &xhci->op_regs->cmd_ring[1]); xhci_writel(xhci, 0, &xhci->op_regs->cmd_ring[0]); + xhci_writel(xhci, 0, &xhci->op_regs->cmd_ring[1]); if (xhci->cmd_ring) xhci_ring_free(xhci, xhci->cmd_ring); xhci->cmd_ring = NULL; @@ -584,8 +584,8 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci->device_pool = NULL; xhci_dbg(xhci, "Freed device context pool\n"); - xhci_writel(xhci, 0, &xhci->op_regs->dcbaa_ptr[1]); xhci_writel(xhci, 0, &xhci->op_regs->dcbaa_ptr[0]); + xhci_writel(xhci, 0, &xhci->op_regs->dcbaa_ptr[1]); if (xhci->dcbaa) pci_free_consistent(pdev, sizeof(*xhci->dcbaa), xhci->dcbaa, xhci->dcbaa->dma); @@ -645,8 +645,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci->dcbaa->dma = dma; xhci_dbg(xhci, "// Device context base array address = 0x%llx (DMA), %p (virt)\n", (unsigned long long)xhci->dcbaa->dma, xhci->dcbaa); - xhci_writel(xhci, (u32) 0, &xhci->op_regs->dcbaa_ptr[1]); xhci_writel(xhci, dma, &xhci->op_regs->dcbaa_ptr[0]); + xhci_writel(xhci, (u32) 0, &xhci->op_regs->dcbaa_ptr[1]); /* * Initialize the ring segment pool. The ring must be a contiguous @@ -677,10 +677,10 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) val = (val & ~CMD_RING_ADDR_MASK) | (xhci->cmd_ring->first_seg->dma & CMD_RING_ADDR_MASK) | xhci->cmd_ring->cycle_state; - xhci_dbg(xhci, "// Setting command ring address high bits to 0x0\n"); - xhci_writel(xhci, (u32) 0, &xhci->op_regs->cmd_ring[1]); xhci_dbg(xhci, "// Setting command ring address low bits to 0x%x\n", val); xhci_writel(xhci, val, &xhci->op_regs->cmd_ring[0]); + xhci_dbg(xhci, "// Setting command ring address high bits to 0x0\n"); + xhci_writel(xhci, (u32) 0, &xhci->op_regs->cmd_ring[1]); xhci_dbg_cmd_ptrs(xhci); val = xhci_readl(xhci, &xhci->cap_regs->db_off); @@ -720,8 +720,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) /* set ring base address and size for each segment table entry */ for (val = 0, seg = xhci->event_ring->first_seg; val < ERST_NUM_SEGS; val++) { struct xhci_erst_entry *entry = &xhci->erst.entries[val]; - entry->seg_addr[1] = 0; entry->seg_addr[0] = seg->dma; + entry->seg_addr[1] = 0; entry->seg_size = TRBS_PER_SEGMENT; entry->rsvd = 0; seg = seg->next; @@ -739,11 +739,11 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) /* set the segment table base address */ xhci_dbg(xhci, "// Set ERST base address for ir_set 0 = 0x%llx\n", (unsigned long long)xhci->erst.erst_dma_addr); - xhci_writel(xhci, 0, &xhci->ir_set->erst_base[1]); val = xhci_readl(xhci, &xhci->ir_set->erst_base[0]); val &= ERST_PTR_MASK; val |= (xhci->erst.erst_dma_addr & ~ERST_PTR_MASK); xhci_writel(xhci, val, &xhci->ir_set->erst_base[0]); + xhci_writel(xhci, 0, &xhci->ir_set->erst_base[1]); /* Set the event ring dequeue address */ set_hc_event_deq(xhci); -- cgit v1.2.3 From 06e7a1487b61e1ae909c4a4c264b4428c55beb7e Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Wed, 29 Apr 2009 19:04:59 -0700 Subject: USB: xhci: Fix Link TRB handoff bit twiddling. Make sure to preserve all bits *except* the TRB_CHAIN bit when giving a Link TRB to the hardware. We need to save things like TRB type and the toggle bit in the control dword. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-ring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 9d6874710669..8fb5d52c08c9 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -188,7 +188,7 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, bool consumer next->link.control &= (u32) ~TRB_CYCLE; else next->link.control |= (u32) TRB_CYCLE; - next->link.control &= TRB_CHAIN; + next->link.control &= ~TRB_CHAIN; next->link.control |= chain; } /* Toggle the cycle bit after the last ring segment. */ -- cgit v1.2.3 From 23e3be113f42790736319c049c78e5f9a4394c02 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Wed, 29 Apr 2009 19:05:20 -0700 Subject: USB: xhci: Avoid global namespace pollution. Make all globally visible functions start with xhci_ and mark functions as static if they're only called within the same C file. Fix some long lines while we're at it. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-dbg.c | 14 ++++----- drivers/usb/host/xhci-hcd.c | 42 ++++++++++++++------------ drivers/usb/host/xhci-mem.c | 2 +- drivers/usb/host/xhci-ring.c | 72 +++++++++++++++++++++++--------------------- drivers/usb/host/xhci.h | 24 +++++++++------ 5 files changed, 82 insertions(+), 72 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 264c38059d4a..6473cbf329f9 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -55,7 +55,7 @@ void xhci_dbg_regs(struct xhci_hcd *xhci) xhci_dbg(xhci, "// Doorbell array at %p:\n", xhci->dba); } -void xhci_print_cap_regs(struct xhci_hcd *xhci) +static void xhci_print_cap_regs(struct xhci_hcd *xhci) { u32 temp; @@ -106,7 +106,7 @@ void xhci_print_cap_regs(struct xhci_hcd *xhci) xhci_dbg(xhci, "RTSOFF 0x%x:\n", temp & RTSOFF_MASK); } -void xhci_print_command_reg(struct xhci_hcd *xhci) +static void xhci_print_command_reg(struct xhci_hcd *xhci) { u32 temp; @@ -124,7 +124,7 @@ void xhci_print_command_reg(struct xhci_hcd *xhci) (temp & CMD_LRESET) ? "not " : ""); } -void xhci_print_status(struct xhci_hcd *xhci) +static void xhci_print_status(struct xhci_hcd *xhci) { u32 temp; @@ -138,14 +138,14 @@ void xhci_print_status(struct xhci_hcd *xhci) (temp & STS_HALT) ? "halted" : "running"); } -void xhci_print_op_regs(struct xhci_hcd *xhci) +static void xhci_print_op_regs(struct xhci_hcd *xhci) { xhci_dbg(xhci, "xHCI operational registers at %p:\n", xhci->op_regs); xhci_print_command_reg(xhci); xhci_print_status(xhci); } -void xhci_print_ports(struct xhci_hcd *xhci) +static void xhci_print_ports(struct xhci_hcd *xhci) { u32 __iomem *addr; int i, j; @@ -340,13 +340,13 @@ void xhci_dbg_ring_ptrs(struct xhci_hcd *xhci, struct xhci_ring *ring) { xhci_dbg(xhci, "Ring deq = %p (virt), 0x%llx (dma)\n", ring->dequeue, - (unsigned long long)trb_virt_to_dma(ring->deq_seg, + (unsigned long long)xhci_trb_virt_to_dma(ring->deq_seg, ring->dequeue)); xhci_dbg(xhci, "Ring deq updated %u times\n", ring->deq_updates); xhci_dbg(xhci, "Ring enq = %p (virt), 0x%llx (dma)\n", ring->enqueue, - (unsigned long long)trb_virt_to_dma(ring->enq_seg, + (unsigned long long)xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue)); xhci_dbg(xhci, "Ring enq updated %u times\n", ring->enq_updates); diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 13188077387c..94447bcdf19f 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -291,7 +291,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) } #ifdef CONFIG_USB_XHCI_HCD_DEBUGGING -void event_ring_work(unsigned long arg) +void xhci_event_ring_work(unsigned long arg) { unsigned long flags; int temp; @@ -330,8 +330,8 @@ void event_ring_work(unsigned long arg) } if (xhci->noops_submitted != NUM_TEST_NOOPS) - if (setup_one_noop(xhci)) - ring_cmd_db(xhci); + if (xhci_setup_one_noop(xhci)) + xhci_ring_cmd_db(xhci); spin_unlock_irqrestore(&xhci->lock, flags); if (!xhci->zombie) @@ -374,7 +374,7 @@ int xhci_run(struct usb_hcd *hcd) #ifdef CONFIG_USB_XHCI_HCD_DEBUGGING init_timer(&xhci->event_ring_timer); xhci->event_ring_timer.data = (unsigned long) xhci; - xhci->event_ring_timer.function = event_ring_work; + xhci->event_ring_timer.function = xhci_event_ring_work; /* Poll the event ring */ xhci->event_ring_timer.expires = jiffies + POLL_TIMEOUT * HZ; xhci->zombie = 0; @@ -404,7 +404,7 @@ int xhci_run(struct usb_hcd *hcd) xhci_print_ir_set(xhci, xhci->ir_set, 0); if (NUM_TEST_NOOPS > 0) - doorbell = setup_one_noop(xhci); + doorbell = xhci_setup_one_noop(xhci); xhci_dbg(xhci, "Command ring memory map follows:\n"); xhci_debug_ring(xhci, xhci->cmd_ring); @@ -600,9 +600,11 @@ int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) goto exit; } if (usb_endpoint_xfer_control(&urb->ep->desc)) - ret = queue_ctrl_tx(xhci, mem_flags, urb, slot_id, ep_index); + ret = xhci_queue_ctrl_tx(xhci, mem_flags, urb, + slot_id, ep_index); else if (usb_endpoint_xfer_bulk(&urb->ep->desc)) - ret = queue_bulk_tx(xhci, mem_flags, urb, slot_id, ep_index); + ret = xhci_queue_bulk_tx(xhci, mem_flags, urb, + slot_id, ep_index); else ret = -EINVAL; exit: @@ -668,8 +670,8 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) * the first cancellation to be handled. */ if (ep_ring->cancels_pending == 1) { - queue_stop_endpoint(xhci, urb->dev->slot_id, ep_index); - ring_cmd_db(xhci); + xhci_queue_stop_endpoint(xhci, urb->dev->slot_id, ep_index); + xhci_ring_cmd_db(xhci); } done: spin_unlock_irqrestore(&xhci->lock, flags); @@ -913,13 +915,14 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) xhci_dbg_ctx(xhci, virt_dev->in_ctx, virt_dev->in_ctx_dma, LAST_CTX_TO_EP_NUM(virt_dev->in_ctx->slot.dev_info)); - ret = queue_configure_endpoint(xhci, virt_dev->in_ctx_dma, udev->slot_id); + ret = xhci_queue_configure_endpoint(xhci, virt_dev->in_ctx_dma, + udev->slot_id); if (ret < 0) { xhci_dbg(xhci, "FIXME allocate a new ring segment\n"); spin_unlock_irqrestore(&xhci->lock, flags); return -ENOMEM; } - ring_cmd_db(xhci); + xhci_ring_cmd_db(xhci); spin_unlock_irqrestore(&xhci->lock, flags); /* Wait for the configure endpoint command to complete */ @@ -1033,12 +1036,12 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) return; spin_lock_irqsave(&xhci->lock, flags); - if (queue_slot_control(xhci, TRB_DISABLE_SLOT, udev->slot_id)) { + if (xhci_queue_slot_control(xhci, TRB_DISABLE_SLOT, udev->slot_id)) { spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); return; } - ring_cmd_db(xhci); + xhci_ring_cmd_db(xhci); spin_unlock_irqrestore(&xhci->lock, flags); /* * Event command completion handler will free any data structures @@ -1058,13 +1061,13 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) int ret; spin_lock_irqsave(&xhci->lock, flags); - ret = queue_slot_control(xhci, TRB_ENABLE_SLOT, 0); + ret = xhci_queue_slot_control(xhci, TRB_ENABLE_SLOT, 0); if (ret) { spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); return 0; } - ring_cmd_db(xhci); + xhci_ring_cmd_db(xhci); spin_unlock_irqrestore(&xhci->lock, flags); /* XXX: how much time for xHC slot assignment? */ @@ -1086,8 +1089,8 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) if (!xhci_alloc_virt_device(xhci, xhci->slot_id, udev, GFP_KERNEL)) { /* Disable slot, if we can do it without mem alloc */ xhci_warn(xhci, "Could not allocate xHCI USB device data structures\n"); - if (!queue_slot_control(xhci, TRB_DISABLE_SLOT, udev->slot_id)) - ring_cmd_db(xhci); + if (!xhci_queue_slot_control(xhci, TRB_DISABLE_SLOT, udev->slot_id)) + xhci_ring_cmd_db(xhci); spin_unlock_irqrestore(&xhci->lock, flags); return 0; } @@ -1129,13 +1132,14 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) xhci_setup_addressable_virt_dev(xhci, udev); /* Otherwise, assume the core has the device configured how it wants */ - ret = queue_address_device(xhci, virt_dev->in_ctx_dma, udev->slot_id); + ret = xhci_queue_address_device(xhci, virt_dev->in_ctx_dma, + udev->slot_id); if (ret) { spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); return ret; } - ring_cmd_db(xhci); + xhci_ring_cmd_db(xhci); spin_unlock_irqrestore(&xhci->lock, flags); /* ctrl tx can take up to 5 sec; XXX: need more time for xHC? */ diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 6523e399fe73..f49f280cfd43 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -746,7 +746,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci_writel(xhci, 0, &xhci->ir_set->erst_base[1]); /* Set the event ring dequeue address */ - set_hc_event_deq(xhci); + xhci_set_hc_event_deq(xhci); xhci_dbg(xhci, "Wrote ERST address to ir_set 0.\n"); xhci_print_ir_set(xhci, xhci->ir_set, 0); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 8fb5d52c08c9..f692e74f269c 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -71,7 +71,7 @@ * Returns zero if the TRB isn't in this segment, otherwise it returns the DMA * address of the TRB. */ -dma_addr_t trb_virt_to_dma(struct xhci_segment *seg, +dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb) { dma_addr_t offset; @@ -235,12 +235,12 @@ static int room_on_ring(struct xhci_hcd *xhci, struct xhci_ring *ring, return 1; } -void set_hc_event_deq(struct xhci_hcd *xhci) +void xhci_set_hc_event_deq(struct xhci_hcd *xhci) { u32 temp; dma_addr_t deq; - deq = trb_virt_to_dma(xhci->event_ring->deq_seg, + deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg, xhci->event_ring->dequeue); if (deq == 0 && !in_interrupt()) xhci_warn(xhci, "WARN something wrong with SW event ring " @@ -256,7 +256,7 @@ void set_hc_event_deq(struct xhci_hcd *xhci) } /* Ring the host controller doorbell after placing a command on the ring */ -void ring_cmd_db(struct xhci_hcd *xhci) +void xhci_ring_cmd_db(struct xhci_hcd *xhci) { u32 temp; @@ -371,7 +371,7 @@ static void find_new_dequeue_state(struct xhci_hcd *xhci, ep_ring->deq_seg = state->new_deq_seg; } -void td_to_noop(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, +static void td_to_noop(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, struct xhci_td *cur_td) { struct xhci_segment *cur_seg; @@ -390,7 +390,7 @@ void td_to_noop(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, xhci_dbg(xhci, "Address = %p (0x%llx dma); " "in seg %p (0x%llx dma)\n", cur_trb, - (unsigned long long)trb_virt_to_dma(cur_seg, cur_trb), + (unsigned long long)xhci_trb_virt_to_dma(cur_seg, cur_trb), cur_seg, (unsigned long long)cur_seg->dma); } else { @@ -403,7 +403,7 @@ void td_to_noop(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, xhci_dbg(xhci, "Cancel TRB %p (0x%llx dma) " "in seg %p (0x%llx dma)\n", cur_trb, - (unsigned long long)trb_virt_to_dma(cur_seg, cur_trb), + (unsigned long long)xhci_trb_virt_to_dma(cur_seg, cur_trb), cur_seg, (unsigned long long)cur_seg->dma); } @@ -458,7 +458,7 @@ static void handle_stopped_endpoint(struct xhci_hcd *xhci, cur_td = list_entry(entry, struct xhci_td, cancelled_td_list); xhci_dbg(xhci, "Cancelling TD starting at %p, 0x%llx (dma).\n", cur_td->first_trb, - (unsigned long long)trb_virt_to_dma(cur_td->start_seg, cur_td->first_trb)); + (unsigned long long)xhci_trb_virt_to_dma(cur_td->start_seg, cur_td->first_trb)); /* * If we stopped on the TD we need to cancel, then we have to * move the xHC endpoint ring dequeue pointer past this TD. @@ -485,7 +485,7 @@ static void handle_stopped_endpoint(struct xhci_hcd *xhci, deq_state.new_deq_seg, (unsigned long long)deq_state.new_deq_seg->dma, deq_state.new_deq_ptr, - (unsigned long long)trb_virt_to_dma(deq_state.new_deq_seg, deq_state.new_deq_ptr), + (unsigned long long)xhci_trb_virt_to_dma(deq_state.new_deq_seg, deq_state.new_deq_ptr), deq_state.new_cycle_state); queue_set_tr_deq(xhci, slot_id, ep_index, deq_state.new_deq_seg, @@ -497,7 +497,7 @@ static void handle_stopped_endpoint(struct xhci_hcd *xhci, * ring running. */ ep_ring->state |= SET_DEQ_PENDING; - ring_cmd_db(xhci); + xhci_ring_cmd_db(xhci); } else { /* Otherwise just ring the doorbell to restart the ring */ ring_ep_doorbell(xhci, slot_id, ep_index); @@ -612,7 +612,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, dma_addr_t cmd_dequeue_dma; cmd_dma = (((u64) event->cmd_trb[1]) << 32) + event->cmd_trb[0]; - cmd_dequeue_dma = trb_virt_to_dma(xhci->cmd_ring->deq_seg, + cmd_dequeue_dma = xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, xhci->cmd_ring->dequeue); /* Is the command ring deq ptr out of sync with the deq seg ptr? */ if (cmd_dequeue_dma == 0) { @@ -677,7 +677,7 @@ static void handle_port_status(struct xhci_hcd *xhci, /* Update event ring dequeue pointer before dropping the lock */ inc_deq(xhci, xhci->event_ring, true); - set_hc_event_deq(xhci); + xhci_set_hc_event_deq(xhci); spin_unlock(&xhci->lock); /* Pass this up to the core */ @@ -702,15 +702,15 @@ static struct xhci_segment *trb_in_td( dma_addr_t end_trb_dma; struct xhci_segment *cur_seg; - start_dma = trb_virt_to_dma(start_seg, start_trb); + start_dma = xhci_trb_virt_to_dma(start_seg, start_trb); cur_seg = start_seg; do { /* We may get an event for a Link TRB in the middle of a TD */ - end_seg_dma = trb_virt_to_dma(cur_seg, + end_seg_dma = xhci_trb_virt_to_dma(cur_seg, &start_seg->trbs[TRBS_PER_SEGMENT - 1]); /* If the end TRB isn't in this segment, this is set to 0 */ - end_trb_dma = trb_virt_to_dma(cur_seg, end_trb); + end_trb_dma = xhci_trb_virt_to_dma(cur_seg, end_trb); if (end_trb_dma > 0) { /* The end TRB is in this segment, so suspect should be here */ @@ -734,7 +734,7 @@ static struct xhci_segment *trb_in_td( return cur_seg; } cur_seg = cur_seg->next; - start_dma = trb_virt_to_dma(cur_seg, &cur_seg->trbs[0]); + start_dma = xhci_trb_virt_to_dma(cur_seg, &cur_seg->trbs[0]); } while (1); } @@ -992,7 +992,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, } cleanup: inc_deq(xhci, xhci->event_ring, true); - set_hc_event_deq(xhci); + xhci_set_hc_event_deq(xhci); /* FIXME for multi-TD URBs (who have buffers bigger than 64MB) */ if (urb) { @@ -1050,7 +1050,7 @@ void xhci_handle_event(struct xhci_hcd *xhci) if (update_ptrs) { /* Update SW and HC event ring dequeue pointer */ inc_deq(xhci, xhci->event_ring, true); - set_hc_event_deq(xhci); + xhci_set_hc_event_deq(xhci); } /* Are there more items on the event ring? */ xhci_handle_event(xhci); @@ -1119,7 +1119,7 @@ static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, return 0; } -int xhci_prepare_transfer(struct xhci_hcd *xhci, +static int prepare_transfer(struct xhci_hcd *xhci, struct xhci_virt_device *xdev, unsigned int ep_index, unsigned int num_trbs, @@ -1156,7 +1156,7 @@ int xhci_prepare_transfer(struct xhci_hcd *xhci, return 0; } -unsigned int count_sg_trbs_needed(struct xhci_hcd *xhci, struct urb *urb) +static unsigned int count_sg_trbs_needed(struct xhci_hcd *xhci, struct urb *urb) { int num_sgs, num_trbs, running_total, temp, i; struct scatterlist *sg; @@ -1200,7 +1200,7 @@ unsigned int count_sg_trbs_needed(struct xhci_hcd *xhci, struct urb *urb) return num_trbs; } -void check_trb_math(struct urb *urb, int num_trbs, int running_total) +static void check_trb_math(struct urb *urb, int num_trbs, int running_total) { if (num_trbs != 0) dev_dbg(&urb->dev->dev, "%s - ep %#x - Miscalculated number of " @@ -1216,7 +1216,7 @@ void check_trb_math(struct urb *urb, int num_trbs, int running_total) urb->transfer_buffer_length); } -void giveback_first_trb(struct xhci_hcd *xhci, int slot_id, +static void giveback_first_trb(struct xhci_hcd *xhci, int slot_id, unsigned int ep_index, int start_cycle, struct xhci_generic_trb *start_trb, struct xhci_td *td) { @@ -1229,7 +1229,7 @@ void giveback_first_trb(struct xhci_hcd *xhci, int slot_id, ring_ep_doorbell(xhci, slot_id, ep_index); } -int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags, +static int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index) { struct xhci_ring *ep_ring; @@ -1248,7 +1248,7 @@ int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags, num_trbs = count_sg_trbs_needed(xhci, urb); num_sgs = urb->num_sgs; - trb_buff_len = xhci_prepare_transfer(xhci, xhci->devs[slot_id], + trb_buff_len = prepare_transfer(xhci, xhci->devs[slot_id], ep_index, num_trbs, urb, &td, mem_flags); if (trb_buff_len < 0) return trb_buff_len; @@ -1356,7 +1356,7 @@ int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags, } /* This is very similar to what ehci-q.c qtd_fill() does */ -int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, +int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index) { struct xhci_ring *ep_ring; @@ -1400,7 +1400,7 @@ int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, (unsigned long long)urb->transfer_dma, num_trbs); - ret = xhci_prepare_transfer(xhci, xhci->devs[slot_id], ep_index, + ret = prepare_transfer(xhci, xhci->devs[slot_id], ep_index, num_trbs, urb, &td, mem_flags); if (ret < 0) return ret; @@ -1469,7 +1469,7 @@ int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, } /* Caller must have locked xhci->lock */ -int queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, +int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index) { struct xhci_ring *ep_ring; @@ -1502,7 +1502,7 @@ int queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, */ if (urb->transfer_buffer_length > 0) num_trbs++; - ret = xhci_prepare_transfer(xhci, xhci->devs[slot_id], ep_index, num_trbs, + ret = prepare_transfer(xhci, xhci->devs[slot_id], ep_index, num_trbs, urb, &td, mem_flags); if (ret < 0) return ret; @@ -1584,36 +1584,38 @@ static int queue_cmd_noop(struct xhci_hcd *xhci) * Place a no-op command on the command ring to test the command and * event ring. */ -void *setup_one_noop(struct xhci_hcd *xhci) +void *xhci_setup_one_noop(struct xhci_hcd *xhci) { if (queue_cmd_noop(xhci) < 0) return NULL; xhci->noops_submitted++; - return ring_cmd_db; + return xhci_ring_cmd_db; } /* Queue a slot enable or disable request on the command ring */ -int queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id) +int xhci_queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id) { return queue_command(xhci, 0, 0, 0, TRB_TYPE(trb_type) | SLOT_ID_FOR_TRB(slot_id)); } /* Queue an address device command TRB */ -int queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id) +int xhci_queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, + u32 slot_id) { return queue_command(xhci, in_ctx_ptr, 0, 0, TRB_TYPE(TRB_ADDR_DEV) | SLOT_ID_FOR_TRB(slot_id)); } /* Queue a configure endpoint command TRB */ -int queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id) +int xhci_queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, + u32 slot_id) { return queue_command(xhci, in_ctx_ptr, 0, 0, TRB_TYPE(TRB_CONFIG_EP) | SLOT_ID_FOR_TRB(slot_id)); } -int queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, +int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, unsigned int ep_index) { u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); @@ -1636,7 +1638,7 @@ static int queue_set_tr_deq(struct xhci_hcd *xhci, int slot_id, u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); u32 type = TRB_TYPE(TRB_SET_DEQ); - addr = trb_virt_to_dma(deq_seg, deq_ptr); + addr = xhci_trb_virt_to_dma(deq_seg, deq_ptr); if (addr == 0) xhci_warn(xhci, "WARN Cannot submit Set TR Deq Ptr\n"); xhci_warn(xhci, "WARN deq seg = %p, deq pt = %p\n", diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 13c9166e758a..df8778e1cfc6 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1130,18 +1130,22 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); /* xHCI ring, segment, TRB, and TD functions */ -dma_addr_t trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); -void ring_cmd_db(struct xhci_hcd *xhci); -void *setup_one_noop(struct xhci_hcd *xhci); +dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); +void xhci_ring_cmd_db(struct xhci_hcd *xhci); +void *xhci_setup_one_noop(struct xhci_hcd *xhci); void xhci_handle_event(struct xhci_hcd *xhci); -void set_hc_event_deq(struct xhci_hcd *xhci); -int queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id); -int queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); -int queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, +void xhci_set_hc_event_deq(struct xhci_hcd *xhci); +int xhci_queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id); +int xhci_queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, + u32 slot_id); +int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, unsigned int ep_index); -int queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); -int queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); -int queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); +int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, + int slot_id, unsigned int ep_index); +int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, + int slot_id, unsigned int ep_index); +int xhci_queue_configure_endpoint(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, + u32 slot_id); /* xHCI roothub code */ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, -- cgit v1.2.3 From c96a2b81f3747e6924307714666aa2368bc1718b Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Wed, 29 Apr 2009 19:05:40 -0700 Subject: USB: xhci: Clean up xhci_irq() function. Drop spinlock in xhci_irq() error path. This fixes the issue reported by Oliver Neukum on this thread: http://marc.info/?l=linux-usb&m=124090924401444&w=2 Remove unnecessary register read reported by Viral Mehta: http://marc.info/?l=linux-usb&m=124091326007398&w=2 Reported-by: Oliver Neukum <oliver@neukum.org> Reported-by: Viral Mehta <viral.mehta@einfochips.com> Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 94447bcdf19f..57aed12b63d2 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -276,11 +276,11 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) return IRQ_NONE; } - temp = xhci_readl(xhci, &xhci->op_regs->status); if (temp & STS_FATAL) { xhci_warn(xhci, "WARNING: Host System Error\n"); xhci_halt(xhci); xhci_to_hcd(xhci)->state = HC_STATE_HALT; + spin_unlock(&xhci->lock); return -ESHUTDOWN; } -- cgit v1.2.3 From b7116ebca4e1a898f30ae474151fd6474327257c Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Wed, 29 Apr 2009 19:05:58 -0700 Subject: USB: xhci: Avoid compiler reordering in Link TRB giveback. Force the compiler to write the cycle bit of the Link TRB last. This ensures that the hardware doesn't think it owns the Link TRB before we set the chain bit. Reported by Oliver in this thread: http://marc.info/?l=linux-usb&m=124091532410219&w=2 Reported-by: Oliver Neukum <oliver@neukum.org> Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-ring.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index f692e74f269c..d42a738cdaa7 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -183,13 +183,14 @@ static void inc_enq(struct xhci_hcd *xhci, struct xhci_ring *ring, bool consumer while (last_trb(xhci, ring, ring->enq_seg, next)) { if (!consumer) { if (ring != xhci->event_ring) { + next->link.control &= ~TRB_CHAIN; + next->link.control |= chain; /* Give this link TRB to the hardware */ + wmb(); if (next->link.control & TRB_CYCLE) next->link.control &= (u32) ~TRB_CYCLE; else next->link.control |= (u32) TRB_CYCLE; - next->link.control &= ~TRB_CHAIN; - next->link.control |= chain; } /* Toggle the cycle bit after the last ring segment. */ if (last_trb_on_last_seg(xhci, ring, ring->enq_seg, next)) { -- cgit v1.2.3 From f0058c627855ecb3b6c7185b7ad1910463c24c42 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Wed, 29 Apr 2009 19:06:20 -0700 Subject: USB: Change names of SuperSpeed ep companion descriptor structs. Differentiate between SuperSpeed endpoint companion descriptor and the wireless USB endpoint companion descriptor. Make all structure names for this descriptor have "ss" (SuperSpeed) in them. David Vrabel asked for this change in http://marc.info/?l=linux-usb&m=124091465109367&w=2 Reported-by: David Vrabel <david.vrabel@csr.com> Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/config.c | 36 +++++++++++++++++++----------------- drivers/usb/host/xhci-mem.c | 2 +- include/linux/usb.h | 14 +++++++------- include/linux/usb/ch9.h | 4 ++-- 4 files changed, 29 insertions(+), 27 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 7103758bb486..24dfb33f90cb 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -69,30 +69,31 @@ static int find_next_descriptor(unsigned char *buffer, int size, return buffer - buffer0; } -static int usb_parse_endpoint_companion(struct device *ddev, int cfgno, +static int usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno, int inum, int asnum, struct usb_host_endpoint *ep, int num_ep, unsigned char *buffer, int size) { unsigned char *buffer_start = buffer; - struct usb_ep_comp_descriptor *desc; + struct usb_ss_ep_comp_descriptor *desc; int retval; int num_skipped; int max_tx; int i; - /* Allocate space for the companion descriptor */ - ep->ep_comp = kzalloc(sizeof(struct usb_host_ep_comp), GFP_KERNEL); - if (!ep->ep_comp) + /* Allocate space for the SS endpoint companion descriptor */ + ep->ss_ep_comp = kzalloc(sizeof(struct usb_host_ss_ep_comp), + GFP_KERNEL); + if (!ep->ss_ep_comp) return -ENOMEM; - desc = (struct usb_ep_comp_descriptor *) buffer; + desc = (struct usb_ss_ep_comp_descriptor *) buffer; if (desc->bDescriptorType != USB_DT_SS_ENDPOINT_COMP) { dev_warn(ddev, "No SuperSpeed endpoint companion for config %d " " interface %d altsetting %d ep %d: " "using minimum values\n", cfgno, inum, asnum, ep->desc.bEndpointAddress); - ep->ep_comp->desc.bLength = USB_DT_EP_COMP_SIZE; - ep->ep_comp->desc.bDescriptorType = USB_DT_SS_ENDPOINT_COMP; - ep->ep_comp->desc.bMaxBurst = 0; + ep->ss_ep_comp->desc.bLength = USB_DT_SS_EP_COMP_SIZE; + ep->ss_ep_comp->desc.bDescriptorType = USB_DT_SS_ENDPOINT_COMP; + ep->ss_ep_comp->desc.bMaxBurst = 0; /* * Leave bmAttributes as zero, which will mean no streams for * bulk, and isoc won't support multiple bursts of packets. @@ -102,7 +103,7 @@ static int usb_parse_endpoint_companion(struct device *ddev, int cfgno, */ if (usb_endpoint_xfer_isoc(&ep->desc) || usb_endpoint_xfer_int(&ep->desc)) - ep->ep_comp->desc.wBytesPerInterval = + ep->ss_ep_comp->desc.wBytesPerInterval = ep->desc.wMaxPacketSize; /* * The next descriptor is for an Endpoint or Interface, @@ -112,16 +113,16 @@ static int usb_parse_endpoint_companion(struct device *ddev, int cfgno, retval = 0; goto valid; } - memcpy(&ep->ep_comp->desc, desc, USB_DT_EP_COMP_SIZE); - desc = &ep->ep_comp->desc; + memcpy(&ep->ss_ep_comp->desc, desc, USB_DT_SS_EP_COMP_SIZE); + desc = &ep->ss_ep_comp->desc; buffer += desc->bLength; size -= desc->bLength; /* Eat up the other descriptors we don't care about */ - ep->ep_comp->extra = buffer; + ep->ss_ep_comp->extra = buffer; i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT, USB_DT_INTERFACE, &num_skipped); - ep->ep_comp->extralen = i; + ep->ss_ep_comp->extralen = i; buffer += i; size -= i; retval = buffer - buffer_start + i; @@ -310,7 +311,7 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum, cfgno, inum, asnum, d->bEndpointAddress, maxp); } - /* Allocate room for and parse any endpoint companion descriptors */ + /* Allocate room for and parse any SS endpoint companion descriptors */ if (to_usb_device(ddev)->speed == USB_SPEED_SUPER) { endpoint->extra = buffer; i = find_next_descriptor_more(buffer, size, USB_DT_SS_ENDPOINT_COMP, @@ -320,8 +321,9 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum, size -= i; if (size > 0) { - retval = usb_parse_endpoint_companion(ddev, cfgno, inum, asnum, - endpoint, num_ep, buffer, size); + retval = usb_parse_ss_endpoint_companion(ddev, cfgno, + inum, asnum, endpoint, num_ep, buffer, + size); if (retval >= 0) { buffer += retval; retval = buffer - buffer0; diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index f49f280cfd43..a7fbd6c10add 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -496,7 +496,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, max_packet = ep->desc.wMaxPacketSize; ep_ctx->ep_info2 |= MAX_PACKET(max_packet); /* dig out max burst from ep companion desc */ - max_packet = ep->ep_comp->desc.bMaxBurst; + max_packet = ep->ss_ep_comp->desc.bMaxBurst; ep_ctx->ep_info2 |= MAX_BURST(max_packet); break; case USB_SPEED_HIGH: diff --git a/include/linux/usb.h b/include/linux/usb.h index 0a1819a64974..7e6b5259ea35 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -47,15 +47,15 @@ struct ep_device; /* For SS devices */ /** - * struct usb_host_ep_comp - Valid for SuperSpeed devices only + * struct usb_host_ss_ep_comp - Valid for SuperSpeed devices only * @desc: endpoint companion descriptor, wMaxPacketSize in native byteorder * @extra: descriptors following this endpoint companion descriptor * @extralen: how many bytes of "extra" are valid */ -struct usb_host_ep_comp { - struct usb_ep_comp_descriptor desc; - unsigned char *extra; /* Extra descriptors */ - int extralen; +struct usb_host_ss_ep_comp { + struct usb_ss_ep_comp_descriptor desc; + unsigned char *extra; /* Extra descriptors */ + int extralen; }; /** @@ -65,7 +65,7 @@ struct usb_host_ep_comp { * @hcpriv: for use by HCD; typically holds hardware dma queue head (QH) * with one or more transfer descriptors (TDs) per urb * @ep_dev: ep_device for sysfs info - * @ep_comp: companion descriptor information for this endpoint + * @ss_ep_comp: companion descriptor information for this endpoint * @extra: descriptors following this endpoint in the configuration * @extralen: how many bytes of "extra" are valid * @enabled: URBs may be submitted to this endpoint @@ -78,7 +78,7 @@ struct usb_host_endpoint { struct list_head urb_list; void *hcpriv; struct ep_device *ep_dev; /* For sysfs info */ - struct usb_host_ep_comp *ep_comp; /* For SS devices */ + struct usb_host_ss_ep_comp *ss_ep_comp; /* For SS devices */ unsigned char *extra; /* Extra descriptors */ int extralen; diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h index 9e9c5c0a3d72..93223638f702 100644 --- a/include/linux/usb/ch9.h +++ b/include/linux/usb/ch9.h @@ -538,7 +538,7 @@ static inline int usb_endpoint_is_isoc_out( /*-------------------------------------------------------------------------*/ /* USB_DT_SS_ENDPOINT_COMP: SuperSpeed Endpoint Companion descriptor */ -struct usb_ep_comp_descriptor { +struct usb_ss_ep_comp_descriptor { __u8 bLength; __u8 bDescriptorType; @@ -547,7 +547,7 @@ struct usb_ep_comp_descriptor { __u16 wBytesPerInterval; } __attribute__ ((packed)); -#define USB_DT_EP_COMP_SIZE 6 +#define USB_DT_SS_EP_COMP_SIZE 6 /*-------------------------------------------------------------------------*/ -- cgit v1.2.3 From c7959fb265d8d5e00eb6d30717eb63e6189a1f07 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Wed, 29 Apr 2009 19:06:36 -0700 Subject: USB: xhci: drop spinlock in xhci_urb_enqueue() error path. Make sure the error path in xhci_urb_enqueue() releases the spinlock before it returns. Reported by Oliver in http://marc.info/?l=linux-usb&m=124091637311832&w=2 Reported-by: Oliver Neukum <oliver@neukum.org> Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-hcd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 57aed12b63d2..c6b921994b28 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -591,7 +591,8 @@ int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) if (!xhci->devs || !xhci->devs[slot_id]) { if (!in_interrupt()) dev_warn(&urb->dev->dev, "WARN: urb submitted for dev with no Slot ID\n"); - return -EINVAL; + ret = -EINVAL; + goto exit; } if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { if (!in_interrupt()) -- cgit v1.2.3 From 527c6d7f188f116852141f50b4d008f966bb17f8 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Wed, 29 Apr 2009 19:06:56 -0700 Subject: USB: xhci: Make xhci-mem.c include linux/dmapool.h xhci-mem.c includes calls to dma_pool_alloc() and other functions defined in linux/dmapool.h. Make sure to include that header file. Reported-by: Randy Dunlap <randy.dunlap@oracle.com> Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-mem.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index a7fbd6c10add..37a83878a064 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -22,6 +22,7 @@ #include <linux/usb.h> #include <linux/pci.h> +#include <linux/dmapool.h> #include "xhci.h" -- cgit v1.2.3 From 7dd19e69d131ea34f74397559b422511e54d2911 Mon Sep 17 00:00:00 2001 From: Viral Mehta <viral.mehta@einfochips.com> Date: Tue, 5 May 2009 15:54:23 +0530 Subject: USB: xhci: replace if-elseif-else with switch-case Replace if-elseif-else with switch-case to keep the code consistent which is semantically same Switch-case is used here, http://www.spinics.net/lists/linux-usb/msg17201.html Making consistent at other places in usb/core Also easier to read and maintain when USB4.0, 5.0, ... comes Signed-off-by: Viral Mehta <viral.mehta@einfochips.com> Tested-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/core/hcd.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 1609623ec829..ce3f453f02ef 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -488,28 +488,39 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) case DeviceRequest | USB_REQ_GET_DESCRIPTOR: switch (wValue & 0xff00) { case USB_DT_DEVICE << 8: - if (hcd->driver->flags & HCD_USB3) + switch (hcd->driver->flags & HCD_MASK) { + case HCD_USB3: bufp = usb3_rh_dev_descriptor; - else if (hcd->driver->flags & HCD_USB2) + break; + case HCD_USB2: bufp = usb2_rh_dev_descriptor; - else if (hcd->driver->flags & HCD_USB11) + break; + case HCD_USB11: bufp = usb11_rh_dev_descriptor; - else + break; + default: goto error; + } len = 18; if (hcd->has_tt) patch_protocol = 1; break; case USB_DT_CONFIG << 8: - if (hcd->driver->flags & HCD_USB3) { + switch (hcd->driver->flags & HCD_MASK) { + case HCD_USB3: bufp = ss_rh_config_descriptor; len = sizeof ss_rh_config_descriptor; - } else if (hcd->driver->flags & HCD_USB2) { + break; + case HCD_USB2: bufp = hs_rh_config_descriptor; len = sizeof hs_rh_config_descriptor; - } else { + break; + case HCD_USB11: bufp = fs_rh_config_descriptor; len = sizeof fs_rh_config_descriptor; + break; + default: + goto error; } if (device_can_wakeup(&hcd->self.root_hub->dev)) patch_wakeup = 1; -- cgit v1.2.3 From 6071d8363b7b284038069f1795a98372fbc1a48e Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Thu, 14 May 2009 11:44:14 -0700 Subject: usb; xhci: Fix TRB offset calculations. Greg KH introduced a bug into xhci_trb_virt_to_dma() when he changed the type of offset to dma_addr_t from unsigned int and dropped the casts to unsigned int around the virtual address pointer subtraction. trb and seg->trbs are both valid pointers to virtual addresses, so the compiler will mod the subtraction by the size of union trb (16 bytes). segment_offset is an unsigned long, which is guaranteed to be at least as big as a void *. Drop the void * casts in the first if statement because trb and seg->trbs are both pointers of the same type (pointers to union trb). Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-ring.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index d42a738cdaa7..02d81985c454 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -74,16 +74,15 @@ dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb) { - dma_addr_t offset; + unsigned long segment_offset; - if (!seg || !trb || (void *) trb < (void *) seg->trbs) + if (!seg || !trb || trb < seg->trbs) return 0; - /* offset in bytes, since these are byte-addressable */ - offset = trb - seg->trbs; - /* SEGMENT_SIZE in bytes, trbs are 16-byte aligned */ - if (offset > SEGMENT_SIZE || (offset % sizeof(*trb)) != 0) + /* offset in TRBs */ + segment_offset = trb - seg->trbs; + if (segment_offset > TRBS_PER_SEGMENT) return 0; - return seg->dma + offset; + return seg->dma + (segment_offset * sizeof(*trb)); } /* Does this link TRB point to the first segment in a ring, -- cgit v1.2.3 From 98441973105b80e133fcaa47ebf17be1e024ea30 Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Thu, 14 May 2009 11:44:18 -0700 Subject: USB: xhci: Remove packed attribute from structures. The packed attribute allows gcc to muck with the alignment of data structures, which may lead to byte-wise writes that break atomicity of writes. Packed should only be used when the compile may add undesired padding to the structure. Each element of the structure will be aligned by C based on its size and the size of the elements around it. E.g. a u64 would be aligned on an 8 byte boundary, the next u32 would be aligned on a four byte boundary, etc. Since most of the xHCI structures contain only u32 bit values, removing the packed attribute for them should be harmless. (A future patch will change some of the twin 32-bit address fields to one 64-bit field, but all those places have an even number of 32-bit fields before them, so the alignment should be correct.) Add BUILD_BUG_ON statements to check that the compiler doesn't add padding to the data structures that have a hardware-defined layout. While we're modifying the registers, change the name of intr_reg to xhci_intr_reg to avoid global conflicts. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-dbg.c | 2 +- drivers/usb/host/xhci-hcd.c | 19 +++++++++++++++++++ drivers/usb/host/xhci.h | 46 ++++++++++++++++++++++----------------------- 3 files changed, 43 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 6473cbf329f9..2501c571f855 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -169,7 +169,7 @@ static void xhci_print_ports(struct xhci_hcd *xhci) } } -void xhci_print_ir_set(struct xhci_hcd *xhci, struct intr_reg *ir_set, int set_num) +void xhci_print_ir_set(struct xhci_hcd *xhci, struct xhci_intr_reg *ir_set, int set_num) { void *addr; u32 temp; diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index c6b921994b28..59ee61d2a198 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -1243,6 +1243,25 @@ static int __init xhci_hcd_init(void) return retval; } #endif + /* + * Check the compiler generated sizes of structures that must be laid + * out in specific ways for hardware access. + */ + BUILD_BUG_ON(sizeof(struct xhci_doorbell_array) != 256*32/8); + BUILD_BUG_ON(sizeof(struct xhci_slot_ctx) != 8*32/8); + BUILD_BUG_ON(sizeof(struct xhci_ep_ctx) != 8*32/8); + /* xhci_device_control has eight fields, and also + * embeds one xhci_slot_ctx and 31 xhci_ep_ctx + */ + BUILD_BUG_ON(sizeof(struct xhci_device_control) != (8+8+8*31)*32/8); + BUILD_BUG_ON(sizeof(struct xhci_stream_ctx) != 4*32/8); + BUILD_BUG_ON(sizeof(union xhci_trb) != 4*32/8); + BUILD_BUG_ON(sizeof(struct xhci_erst_entry) != 4*32/8); + BUILD_BUG_ON(sizeof(struct xhci_cap_regs) != 7*32/8); + BUILD_BUG_ON(sizeof(struct xhci_intr_reg) != 8*32/8); + /* xhci_run_regs has eight fields and embeds 128 xhci_intr_regs */ + BUILD_BUG_ON(sizeof(struct xhci_run_regs) != (8+8*128)*32/8); + BUILD_BUG_ON(sizeof(struct xhci_doorbell_array) != 256*32/8); return 0; } module_init(xhci_hcd_init); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index df8778e1cfc6..3e8e09c4c2bf 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -71,7 +71,7 @@ struct xhci_cap_regs { u32 db_off; u32 run_regs_off; /* Reserved up to (CAPLENGTH - 0x1C) */ -} __attribute__ ((packed)); +}; /* hc_capbase bitmasks */ /* bits 7:0 - how long is the Capabilities register */ @@ -180,7 +180,7 @@ struct xhci_op_regs { u32 reserved5; /* registers for ports 2-255 */ u32 reserved6[NUM_PORT_REGS*254]; -} __attribute__ ((packed)); +}; /* USBCMD - USB command - command bitmasks */ /* start/stop HC execution - do not write unless HC is halted*/ @@ -361,7 +361,7 @@ struct xhci_op_regs { /** - * struct intr_reg - Interrupt Register Set + * struct xhci_intr_reg - Interrupt Register Set * @irq_pending: IMAN - Interrupt Management Register. Used to enable * interrupts and check for pending interrupts. * @irq_control: IMOD - Interrupt Moderation Register. @@ -377,14 +377,14 @@ struct xhci_op_regs { * position of the Enqueue Pointer." The HCD (Linux) processes those events and * updates the dequeue pointer. */ -struct intr_reg { +struct xhci_intr_reg { u32 irq_pending; u32 irq_control; u32 erst_size; u32 rsvd; u32 erst_base[2]; u32 erst_dequeue[2]; -} __attribute__ ((packed)); +}; /* irq_pending bitmasks */ #define ER_IRQ_PENDING(p) ((p) & 0x1) @@ -428,10 +428,10 @@ struct intr_reg { * or larger accesses" */ struct xhci_run_regs { - u32 microframe_index; - u32 rsvd[7]; - struct intr_reg ir_set[128]; -} __attribute__ ((packed)); + u32 microframe_index; + u32 rsvd[7]; + struct xhci_intr_reg ir_set[128]; +}; /** * struct doorbell_array @@ -440,7 +440,7 @@ struct xhci_run_regs { */ struct xhci_doorbell_array { u32 doorbell[256]; -} __attribute__ ((packed)); +}; #define DB_TARGET_MASK 0xFFFFFF00 #define DB_STREAM_ID_MASK 0x0000FFFF @@ -470,7 +470,7 @@ struct xhci_slot_ctx { u32 dev_state; /* offset 0x10 to 0x1f reserved for HC internal use */ u32 reserved[4]; -} __attribute__ ((packed)); +}; /* dev_info bitmasks */ /* Route String - 0:19 */ @@ -542,7 +542,7 @@ struct xhci_ep_ctx { u32 tx_info; /* offset 0x14 - 0x1f reserved for HC internal use */ u32 reserved[3]; -} __attribute__ ((packed)); +}; /* ep_info bitmasks */ /* @@ -601,7 +601,7 @@ struct xhci_device_control { u32 rsvd[6]; struct xhci_slot_ctx slot; struct xhci_ep_ctx ep[31]; -} __attribute__ ((packed)); +}; /* drop context bitmasks */ #define DROP_EP(x) (0x1 << x) @@ -644,7 +644,7 @@ struct xhci_device_context_array { u32 dev_context_ptrs[2*MAX_HC_SLOTS]; /* private xHCD pointers */ dma_addr_t dma; -} __attribute__ ((packed)); +}; /* TODO: write function to set the 64-bit device DMA address */ /* * TODO: change this to be dynamically sized at HC mem init time since the HC @@ -657,7 +657,7 @@ struct xhci_stream_ctx { u32 stream_ring[2]; /* offset 0x14 - 0x1f reserved for HC internal use */ u32 reserved[2]; -} __attribute__ ((packed)); +}; struct xhci_transfer_event { @@ -666,7 +666,7 @@ struct xhci_transfer_event { u32 transfer_len; /* This field is interpreted differently based on the type of TRB */ u32 flags; -} __attribute__ ((packed)); +}; /** Transfer Event bit fields **/ #define TRB_TO_EP_ID(p) (((p) >> 16) & 0x1f) @@ -747,7 +747,7 @@ struct xhci_link_trb { u32 segment_ptr[2]; u32 intr_target; u32 control; -} __attribute__ ((packed)); +}; /* control bitfields */ #define LINK_TOGGLE (0x1<<1) @@ -758,7 +758,7 @@ struct xhci_event_cmd { u32 cmd_trb[2]; u32 status; u32 flags; -} __attribute__ ((packed)); +}; /* flags bitmasks */ /* bits 16:23 are the virtual function ID */ @@ -809,7 +809,7 @@ struct xhci_event_cmd { struct xhci_generic_trb { u32 field[4]; -} __attribute__ ((packed)); +}; union xhci_trb { struct xhci_link_trb link; @@ -904,7 +904,7 @@ struct xhci_segment { /* private to HCD */ struct xhci_segment *next; dma_addr_t dma; -} __attribute__ ((packed)); +}; struct xhci_td { struct list_head td_list; @@ -946,7 +946,7 @@ struct xhci_erst_entry { u32 seg_size; /* Set to zero */ u32 rsvd; -} __attribute__ ((packed)); +}; struct xhci_erst { struct xhci_erst_entry *entries; @@ -980,7 +980,7 @@ struct xhci_hcd { struct xhci_run_regs __iomem *run_regs; struct xhci_doorbell_array __iomem *dba; /* Our HCD's current interrupter register set */ - struct intr_reg __iomem *ir_set; + struct xhci_intr_reg __iomem *ir_set; /* Cached register copies of read-only HC data */ __u32 hcs_params1; @@ -1079,7 +1079,7 @@ static inline void xhci_writel(struct xhci_hcd *xhci, } /* xHCI debugging */ -void xhci_print_ir_set(struct xhci_hcd *xhci, struct intr_reg *ir_set, int set_num); +void xhci_print_ir_set(struct xhci_hcd *xhci, struct xhci_intr_reg *ir_set, int set_num); void xhci_print_registers(struct xhci_hcd *xhci); void xhci_dbg_regs(struct xhci_hcd *xhci); void xhci_print_run_regs(struct xhci_hcd *xhci); -- cgit v1.2.3 From a4d88302268d1e458f7c6eb7855f1ba4b3a7cffc Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Thu, 14 May 2009 11:44:26 -0700 Subject: USB: xHCI: Fix interrupt moderation. Mask off the lower 16 bits of the interrupt control register, instead of masking off the upper 16 bits. The interrupt moderation interval field is the lower 16 bytes, and is set to 0x4000 (1ms) by default. The previous code was adding 40 us to the default value, instead of setting it to 40 us. This makes performance really bad. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 59ee61d2a198..489657c853e7 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -384,7 +384,7 @@ int xhci_run(struct usb_hcd *hcd) xhci_dbg(xhci, "// Set the interrupt modulation register\n"); temp = xhci_readl(xhci, &xhci->ir_set->irq_control); - temp &= 0xffff; + temp &= ~ER_IRQ_INTERVAL_MASK; temp |= (u32) 160; xhci_writel(xhci, temp, &xhci->ir_set->irq_control); -- cgit v1.2.3 From f88ba78d9ac0e1f583da4cada80b8816ca761a3f Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Thu, 14 May 2009 11:44:22 -0700 Subject: USB: xhci: Respect critical sections. Narrow down time spent holding the xHCI spinlock so that it's only used to protect the xHCI rings, not as mutual exclusion. Stop allocating memory while holding the spinlock and calling xhci_alloc_virt_device() and xhci_endpoint_init(). The USB core should have locking in it to prevent device state to be manipulated by more than one kernel thread. E.g. you can't free a device while you're in the middle of setting a new configuration. So removing the locks from the sections where xhci_alloc_dev() and xhci_reset_bandwidth() touch xHCI's representation of the device should be OK. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/xhci-hcd.c | 63 ++++++++++++++++++++++----------------------- drivers/usb/host/xhci-mem.c | 5 ++-- drivers/usb/host/xhci.h | 4 ++- 3 files changed, 37 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 489657c853e7..dba3e07ccd09 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -687,11 +687,14 @@ done: * different endpoint descriptor in usb_host_endpoint. * A call to xhci_add_endpoint() followed by a call to xhci_drop_endpoint() is * not allowed. + * + * The USB core will not allow URBs to be queued to an endpoint that is being + * disabled, so there's no need for mutual exclusion to protect + * the xhci->devs[slot_id] structure. */ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep) { - unsigned long flags; struct xhci_hcd *xhci; struct xhci_device_control *in_ctx; unsigned int last_ctx; @@ -714,11 +717,9 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, return 0; } - spin_lock_irqsave(&xhci->lock, flags); if (!xhci->devs || !xhci->devs[udev->slot_id]) { xhci_warn(xhci, "xHCI %s called with unaddressed device\n", __func__); - spin_unlock_irqrestore(&xhci->lock, flags); return -EINVAL; } @@ -732,7 +733,6 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, in_ctx->drop_flags & xhci_get_endpoint_flag(&ep->desc)) { xhci_warn(xhci, "xHCI %s called with disabled ep %p\n", __func__, ep); - spin_unlock_irqrestore(&xhci->lock, flags); return 0; } @@ -752,8 +752,6 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, xhci_endpoint_zero(xhci, xhci->devs[udev->slot_id], ep); - spin_unlock_irqrestore(&xhci->lock, flags); - xhci_dbg(xhci, "drop ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x, new slot info = %#x\n", (unsigned int) ep->desc.bEndpointAddress, udev->slot_id, @@ -771,11 +769,14 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, * different endpoint descriptor in usb_host_endpoint. * A call to xhci_add_endpoint() followed by a call to xhci_drop_endpoint() is * not allowed. + * + * The USB core will not allow URBs to be queued to an endpoint until the + * configuration or alt setting is installed in the device, so there's no need + * for mutual exclusion to protect the xhci->devs[slot_id] structure. */ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep) { - unsigned long flags; struct xhci_hcd *xhci; struct xhci_device_control *in_ctx; unsigned int ep_index; @@ -802,11 +803,9 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, return 0; } - spin_lock_irqsave(&xhci->lock, flags); if (!xhci->devs || !xhci->devs[udev->slot_id]) { xhci_warn(xhci, "xHCI %s called with unaddressed device\n", __func__); - spin_unlock_irqrestore(&xhci->lock, flags); return -EINVAL; } @@ -819,14 +818,18 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, if (in_ctx->add_flags & xhci_get_endpoint_flag(&ep->desc)) { xhci_warn(xhci, "xHCI %s called with enabled ep %p\n", __func__, ep); - spin_unlock_irqrestore(&xhci->lock, flags); return 0; } - if (xhci_endpoint_init(xhci, xhci->devs[udev->slot_id], udev, ep) < 0) { + /* + * Configuration and alternate setting changes must be done in + * process context, not interrupt context (or so documenation + * for usb_set_interface() and usb_set_configuration() claim). + */ + if (xhci_endpoint_init(xhci, xhci->devs[udev->slot_id], + udev, ep, GFP_KERNEL) < 0) { dev_dbg(&udev->dev, "%s - could not initialize ep %#x\n", __func__, ep->desc.bEndpointAddress); - spin_unlock_irqrestore(&xhci->lock, flags); return -ENOMEM; } @@ -847,7 +850,6 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, in_ctx->slot.dev_info |= LAST_CTX(last_ctx); } new_slot_info = in_ctx->slot.dev_info; - spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg(xhci, "add ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x, new slot info = %#x\n", (unsigned int) ep->desc.bEndpointAddress, @@ -883,6 +885,16 @@ static void xhci_zero_in_ctx(struct xhci_virt_device *virt_dev) } } +/* Called after one or more calls to xhci_add_endpoint() or + * xhci_drop_endpoint(). If this call fails, the USB core is expected + * to call xhci_reset_bandwidth(). + * + * Since we are in the middle of changing either configuration or + * installing a new alt setting, the USB core won't allow URBs to be + * enqueued for any endpoint on the old config or interface. Nothing + * else should be touching the xhci->devs[slot_id] structure, so we + * don't need to take the xhci->lock for manipulating that. + */ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) { int i; @@ -897,11 +909,9 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) return ret; xhci = hcd_to_xhci(hcd); - spin_lock_irqsave(&xhci->lock, flags); if (!udev->slot_id || !xhci->devs || !xhci->devs[udev->slot_id]) { xhci_warn(xhci, "xHCI %s called with unaddressed device\n", __func__); - spin_unlock_irqrestore(&xhci->lock, flags); return -EINVAL; } xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); @@ -916,11 +926,12 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) xhci_dbg_ctx(xhci, virt_dev->in_ctx, virt_dev->in_ctx_dma, LAST_CTX_TO_EP_NUM(virt_dev->in_ctx->slot.dev_info)); + spin_lock_irqsave(&xhci->lock, flags); ret = xhci_queue_configure_endpoint(xhci, virt_dev->in_ctx_dma, udev->slot_id); if (ret < 0) { - xhci_dbg(xhci, "FIXME allocate a new ring segment\n"); spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "FIXME allocate a new ring segment\n"); return -ENOMEM; } xhci_ring_cmd_db(xhci); @@ -937,7 +948,6 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) return -ETIME; } - spin_lock_irqsave(&xhci->lock, flags); switch (virt_dev->cmd_status) { case COMP_ENOMEM: dev_warn(&udev->dev, "Not enough host controller resources " @@ -968,7 +978,6 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) } if (ret) { /* Callee should call reset_bandwidth() */ - spin_unlock_irqrestore(&xhci->lock, flags); return ret; } @@ -986,14 +995,11 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) } } - spin_unlock_irqrestore(&xhci->lock, flags); - return ret; } void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) { - unsigned long flags; struct xhci_hcd *xhci; struct xhci_virt_device *virt_dev; int i, ret; @@ -1003,11 +1009,9 @@ void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) return; xhci = hcd_to_xhci(hcd); - spin_lock_irqsave(&xhci->lock, flags); if (!xhci->devs || !xhci->devs[udev->slot_id]) { xhci_warn(xhci, "xHCI %s called with unaddressed device\n", __func__); - spin_unlock_irqrestore(&xhci->lock, flags); return; } xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); @@ -1020,7 +1024,6 @@ void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) } } xhci_zero_in_ctx(virt_dev); - spin_unlock_irqrestore(&xhci->lock, flags); } /* @@ -1046,7 +1049,7 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) spin_unlock_irqrestore(&xhci->lock, flags); /* * Event command completion handler will free any data structures - * associated with the slot + * associated with the slot. XXX Can free sleep? */ } @@ -1081,15 +1084,15 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) return 0; } - spin_lock_irqsave(&xhci->lock, flags); if (!xhci->slot_id) { xhci_err(xhci, "Error while assigning device slot ID\n"); - spin_unlock_irqrestore(&xhci->lock, flags); return 0; } + /* xhci_alloc_virt_device() does not touch rings; no need to lock */ if (!xhci_alloc_virt_device(xhci, xhci->slot_id, udev, GFP_KERNEL)) { /* Disable slot, if we can do it without mem alloc */ xhci_warn(xhci, "Could not allocate xHCI USB device data structures\n"); + spin_lock_irqsave(&xhci->lock, flags); if (!xhci_queue_slot_control(xhci, TRB_DISABLE_SLOT, udev->slot_id)) xhci_ring_cmd_db(xhci); spin_unlock_irqrestore(&xhci->lock, flags); @@ -1098,7 +1101,6 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) udev->slot_id = xhci->slot_id; /* Is this a LS or FS device under a HS hub? */ /* Hub or peripherial? */ - spin_unlock_irqrestore(&xhci->lock, flags); return 1; } @@ -1125,7 +1127,6 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) return -EINVAL; } - spin_lock_irqsave(&xhci->lock, flags); virt_dev = xhci->devs[udev->slot_id]; /* If this is a Set Address to an unconfigured device, setup ep 0 */ @@ -1133,6 +1134,7 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) xhci_setup_addressable_virt_dev(xhci, udev); /* Otherwise, assume the core has the device configured how it wants */ + spin_lock_irqsave(&xhci->lock, flags); ret = xhci_queue_address_device(xhci, virt_dev->in_ctx_dma, udev->slot_id); if (ret) { @@ -1157,7 +1159,6 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) return -ETIME; } - spin_lock_irqsave(&xhci->lock, flags); switch (virt_dev->cmd_status) { case COMP_CTX_STATE: case COMP_EBADSLT: @@ -1179,7 +1180,6 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) break; } if (ret) { - spin_unlock_irqrestore(&xhci->lock, flags); return ret; } temp = xhci_readl(xhci, &xhci->op_regs->dcbaa_ptr[0]); @@ -1211,7 +1211,6 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) /* Mirror flags in the output context for future ep enable/disable */ virt_dev->out_ctx->add_flags = SLOT_FLAG | EP0_FLAG; virt_dev->out_ctx->drop_flags = 0; - spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg(xhci, "Device address = %d\n", udev->devnum); /* XXX Meh, not sure if anyone else but choose_address uses this. */ diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 37a83878a064..c8a72de1c508 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -460,7 +460,8 @@ static inline u32 xhci_get_endpoint_type(struct usb_device *udev, int xhci_endpoint_init(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, struct usb_device *udev, - struct usb_host_endpoint *ep) + struct usb_host_endpoint *ep, + gfp_t mem_flags) { unsigned int ep_index; struct xhci_ep_ctx *ep_ctx; @@ -472,7 +473,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, ep_ctx = &virt_dev->in_ctx->ep[ep_index]; /* Set up the endpoint ring */ - virt_dev->new_ep_rings[ep_index] = xhci_ring_alloc(xhci, 1, true, GFP_KERNEL); + virt_dev->new_ep_rings[ep_index] = xhci_ring_alloc(xhci, 1, true, mem_flags); if (!virt_dev->new_ep_rings[ep_index]) return -ENOMEM; ep_ring = virt_dev->new_ep_rings[ep_index]; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 3e8e09c4c2bf..8936eeb5588b 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1101,7 +1101,9 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc); unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc); void xhci_endpoint_zero(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, struct usb_host_endpoint *ep); -int xhci_endpoint_init(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, struct usb_device *udev, struct usb_host_endpoint *ep); +int xhci_endpoint_init(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, + struct usb_device *udev, struct usb_host_endpoint *ep, + gfp_t mem_flags); void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring); #ifdef CONFIG_PCI -- cgit v1.2.3 From eb6bab138d8b1172edf67ef7bd2032f476b7c6ad Mon Sep 17 00:00:00 2001 From: Sarah Sharp <sarah.a.sharp@linux.intel.com> Date: Wed, 29 Apr 2009 19:07:13 -0700 Subject: USB: xhci: Add Makefile, MAINTAINERS, and Kconfig entries. Add Makefile and Kconfig entries for the xHCI host controller driver. List Sarah Sharp as the maintainer for the xHCI driver. Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- MAINTAINERS | 6 ++++++ drivers/usb/Makefile | 1 + drivers/usb/host/Kconfig | 20 ++++++++++++++++++++ drivers/usb/host/Makefile | 2 ++ 4 files changed, 29 insertions(+) (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 2cb7566904b1..6ade3a570622 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6164,6 +6164,12 @@ L: linux-wireless@vger.kernel.org S: Maintained F: drivers/net/wireless/rndis_wlan.c +USB XHCI DRIVER +P: Sarah Sharp +M: sarah.a.sharp@intel.com +L: linux-usb@vger.kernel.org +S: Supported + USB ZC0301 DRIVER P: Luca Risolia M: luca.risolia@studio.unibo.it diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 0a3dc5ece634..19cb7d5480d7 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_USB_ISP116X_HCD) += host/ obj-$(CONFIG_USB_OHCI_HCD) += host/ obj-$(CONFIG_USB_UHCI_HCD) += host/ obj-$(CONFIG_USB_FHCI_HCD) += host/ +obj-$(CONFIG_USB_XHCI_HCD) += host/ obj-$(CONFIG_USB_SL811_HCD) += host/ obj-$(CONFIG_USB_U132_HCD) += host/ obj-$(CONFIG_USB_R8A66597_HCD) += host/ diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 845479f7c707..5e2daed6176c 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -17,6 +17,26 @@ config USB_C67X00_HCD To compile this driver as a module, choose M here: the module will be called c67x00. +config USB_XHCI_HCD + tristate "xHCI HCD (USB 3.0) support (EXPERIMENTAL)" + depends on USB && EXPERIMENTAL + ---help--- + The eXtensible Host Controller Interface (xHCI) is standard for USB 3.0 + "SuperSpeed" host controller hardware. + + To compile this driver as a module, choose M here: the + module will be called xhci-hcd. + +config USB_XHCI_HCD_DEBUGGING + bool "Debugging for the xHCI host controller" + depends on USB_XHCI_HCD + ---help--- + Say 'Y' to turn on debugging for the xHCI host controller driver. + This will spew debugging output, even in interrupt context. + This should only be used for debugging xHCI driver bugs. + + If unsure, say N. + config USB_EHCI_HCD tristate "EHCI HCD (USB 2.0) support" depends on USB && USB_ARCH_HAS_EHCI diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index f163571e33d8..289d748bb414 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -12,6 +12,7 @@ fhci-objs := fhci-hcd.o fhci-hub.o fhci-q.o fhci-mem.o \ ifeq ($(CONFIG_FHCI_DEBUG),y) fhci-objs += fhci-dbg.o endif +xhci-objs := xhci-hcd.o xhci-mem.o xhci-pci.o xhci-ring.o xhci-hub.o xhci-dbg.o obj-$(CONFIG_USB_WHCI_HCD) += whci/ @@ -23,6 +24,7 @@ obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o obj-$(CONFIG_USB_FHCI_HCD) += fhci.o +obj-$(CONFIG_USB_XHCI_HCD) += xhci.o obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o -- cgit v1.2.3 From 1b6ed69f974f6f32c8be0d9a7fc952822eb83b6f Mon Sep 17 00:00:00 2001 From: Paul Mundt <lethal@linux-sh.org> Date: Thu, 30 Apr 2009 17:30:02 +0900 Subject: USB: xhci depends on PCI. While it looks like xhci was written with both PCI and non-PCI in mind, apparently only the former has seen any testing. xhci-mem.o can be "fixed" with a linux/dmapool.h include, but there are still parts of the code that make use of struct pci_dev directly. So, at least more work is needed before this can be turned on for non-PCI builds: CC drivers/usb/host/xhci-mem.o drivers/usb/host/xhci-mem.c: In function 'xhci_segment_alloc': drivers/usb/host/xhci-mem.c:45: error: implicit declaration of function 'dma_pool_alloc' drivers/usb/host/xhci-mem.c:45: warning: assignment makes pointer from integer without a cast drivers/usb/host/xhci-mem.c: In function 'xhci_segment_free': drivers/usb/host/xhci-mem.c:67: error: implicit declaration of function 'dma_pool_free' drivers/usb/host/xhci-mem.c: In function 'xhci_alloc_virt_device': drivers/usb/host/xhci-mem.c:239: warning: assignment makes pointer from integer without a cast drivers/usb/host/xhci-mem.c:248: warning: assignment makes pointer from integer without a cast drivers/usb/host/xhci-mem.c: In function 'xhci_mem_cleanup': drivers/usb/host/xhci-mem.c:578: error: implicit declaration of function 'dma_pool_destroy' drivers/usb/host/xhci-mem.c: In function 'xhci_mem_init': drivers/usb/host/xhci-mem.c:657: error: implicit declaration of function 'dma_pool_create' drivers/usb/host/xhci-mem.c:658: warning: assignment makes pointer from integer without a cast drivers/usb/host/xhci-mem.c:663: warning: assignment makes pointer from integer without a cast make[3]: *** [drivers/usb/host/xhci-mem.o] Error 1 CC drivers/usb/host/xhci-pci.o drivers/usb/host/xhci-pci.c: In function 'xhci_pci_reinit': drivers/usb/host/xhci-pci.c:39: error: implicit declaration of function 'pci_set_mwi' drivers/usb/host/xhci-pci.c: At top level: drivers/usb/host/xhci-pci.c:151: error: 'usb_hcd_pci_probe' undeclared here (not in a function) drivers/usb/host/xhci-pci.c:152: error: 'usb_hcd_pci_remove' undeclared here (not in a function) drivers/usb/host/xhci-pci.c:155: error: 'usb_hcd_pci_shutdown' undeclared here (not in a function) drivers/usb/host/xhci-pci.c:159: warning: function declaration isn't a prototype drivers/usb/host/xhci-pci.c:164: warning: function declaration isn't a prototype make[3]: *** [drivers/usb/host/xhci-pci.o] Error 1 Cc: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Paul Mundt <lethal@linux-sh.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 5e2daed6176c..1576a0520adf 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -19,7 +19,7 @@ config USB_C67X00_HCD config USB_XHCI_HCD tristate "xHCI HCD (USB 3.0) support (EXPERIMENTAL)" - depends on USB && EXPERIMENTAL + depends on USB && PCI && EXPERIMENTAL ---help--- The eXtensible Host Controller Interface (xHCI) is standard for USB 3.0 "SuperSpeed" host controller hardware. -- cgit v1.2.3