/*
* MUSB OTG peripheral driver ep0 handling
*
* 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
* 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., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include "musb_core.h"
/* ep0 is always musb->endpoints[0].ep_in */
#define next_ep0_request(musb) next_in_request(&(musb)->endpoints[0])
/*
* locking note: we use only the controller lock, for simpler correctness.
* It's always held with IRQs blocked.
*
* It protects the ep0 request queue as well as ep0_state, not just the
* controller and indexed registers. And that lock stays held unless it
* needs to be dropped to allow reentering this driver ... like upcalls to
* the gadget driver, or adjusting endpoint halt status.
*/
static char *decode_ep0stage(u8 stage)
{
switch (stage) {
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";
case MUSB_EP0_STAGE_STATUSIN: return "in/status";
case MUSB_EP0_STAGE_STATUSOUT: return "out/status";
default: return "?";
}
}
/* handle a standard GET_STATUS request
* Context: caller holds controller lock
*/
static int service_tx_status_request(
struct musb *musb,
const struct usb_ctrlrequest *ctrlrequest)
{
void __iomem *mbase = musb->mregs;
int handled = 1;
u8 result[2], epnum = 0;
const u8 recip = ctrlrequest->bRequestType & USB_RECIP_MASK;
result[1] = 0;
switch (recip) {
case USB_RECIP_DEVICE:
result[0] = musb->is_self_powered << USB_DEVICE_SELF_POWERED;
result[0] |= musb->may_wakeup << USB_DEVICE_REMOTE_WAKEUP;
#ifdef CONFIG_USB_MUSB_OTG
if (musb->g.is_otg) {
result[0] |= musb->g.b_hnp_enable
<< USB_DEVICE_B_HNP_ENABLE;
result[0] |= musb->g.a_alt_hnp_support
<< USB_DEVICE_A_ALT_HNP_SUPPORT;
result[0] |= musb->g.a_hnp_support
<< USB_DEVICE_A_HNP_SUPPORT;
}
#endif
break;
case USB_RECIP_INTERFACE:
result[0] = 0;
break;
case USB_RECIP_ENDPOINT: {
int is_in;
struct musb_ep *ep;
u16 tmp;
void __iomem *regs;
epnum = (u8) ctrlrequest->wIndex;
if (!epnum) {
result[0] = 0;
break;
}
is_in = epnum & USB_DIR_IN;
if (is_in) {
epnum &= 0x0f;
ep = &musb->endpoints[epnum].ep_in;
} else {
ep = &musb->endpoints[epnum].ep_out;
}
regs = musb->endpoints[epnum].regs;
if (epnum >= MUSB_C_NUM_EPS || !ep->desc) {
handled = -EINVAL;
break;
}
musb_ep_select(mbase, epnum);
if (is_in)
tmp = musb_readw(regs, MUSB_TXCSR)
& MUSB_TXCSR_P_SENDSTALL;
else
tmp = musb_readw(regs, MUSB_RXCSR)
& MUSB_RXCSR_P_SENDSTALL;
musb_ep_select(mbase, 0);
result[0] = tmp ? 1 : 0;
} break;
default:
/* class, vendor, etc ... delegate */
handled = 0;
break;
}
/* fill up the fifo; caller updates csr0 */
if (handled > 0) {
u16 len = le16_to_cpu(ctrlrequest->wLength);
if (len > 2)
len = 2;
musb_write_fifo(&