diff options
Diffstat (limited to 'drivers/usb/gadget/udc/aspeed-vhub/ep0.c')
| -rw-r--r-- | drivers/usb/gadget/udc/aspeed-vhub/ep0.c | 72 |
1 files changed, 49 insertions, 23 deletions
diff --git a/drivers/usb/gadget/udc/aspeed-vhub/ep0.c b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c index e2927fb083cf..e9aa74231760 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/ep0.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c @@ -5,11 +5,6 @@ * ep0.c - Endpoint 0 handling * * Copyright 2017 IBM Corporation - * - * 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/kernel.h> @@ -26,7 +21,6 @@ #include <linux/clk.h> #include <linux/usb/gadget.h> #include <linux/of.h> -#include <linux/of_gpio.h> #include <linux/regmap.h> #include <linux/dma-mapping.h> @@ -105,18 +99,20 @@ void ast_vhub_ep0_handle_setup(struct ast_vhub_ep *ep) (crq.bRequestType & USB_DIR_IN) ? "in" : "out", ep->ep0.state); - /* Check our state, cancel pending requests if needed */ - if (ep->ep0.state != ep0_state_token) { + /* + * Check our state, cancel pending requests if needed + * + * Note: Under some circumstances, we can get a new setup + * packet while waiting for the stall ack, just accept it. + * + * In any case, a SETUP packet in wrong state should have + * reset the HW state machine, so let's just log, nuke + * requests, move on. + */ + if (ep->ep0.state != ep0_state_token && + ep->ep0.state != ep0_state_stall) { EPDBG(ep, "wrong state\n"); ast_vhub_nuke(ep, -EIO); - - /* - * Accept the packet regardless, this seems to happen - * when stalling a SETUP packet that has an OUT data - * phase. - */ - ast_vhub_nuke(ep, 0); - goto stall; } /* Calculate next state for EP0 */ @@ -165,7 +161,7 @@ void ast_vhub_ep0_handle_setup(struct ast_vhub_ep *ep) stall: EPDBG(ep, "stalling\n"); writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat); - ep->ep0.state = ep0_state_status; + ep->ep0.state = ep0_state_stall; ep->ep0.dir_in = false; return; @@ -254,6 +250,13 @@ static void ast_vhub_ep0_do_receive(struct ast_vhub_ep *ep, struct ast_vhub_req len = remain; rc = -EOVERFLOW; } + + /* Hardware return wrong data len */ + if (len < ep->ep.maxpacket && len != remain) { + EPDBG(ep, "using expected data len instead\n"); + len = remain; + } + if (len && req->req.buf) memcpy(req->req.buf + req->req.actual, ep->buf, len); req->req.actual += len; @@ -299,8 +302,8 @@ void ast_vhub_ep0_handle_ack(struct ast_vhub_ep *ep, bool in_ack) if ((ep->ep0.dir_in && (stat & VHUB_EP0_TX_BUFF_RDY)) || (!ep->ep0.dir_in && (stat & VHUB_EP0_RX_BUFF_RDY)) || (ep->ep0.dir_in != in_ack)) { + /* In that case, ignore interrupt */ dev_warn(dev, "irq state mismatch"); - stall = true; break; } /* @@ -335,12 +338,22 @@ void ast_vhub_ep0_handle_ack(struct ast_vhub_ep *ep, bool in_ack) dev_warn(dev, "status direction mismatch\n"); stall = true; } + break; + case ep0_state_stall: + /* + * There shouldn't be any request left, but nuke just in case + * otherwise the stale request will block subsequent ones + */ + ast_vhub_nuke(ep, -EIO); + break; } - /* Reset to token state */ - ep->ep0.state = ep0_state_token; - if (stall) + /* Reset to token state or stall */ + if (stall) { writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat); + ep->ep0.state = ep0_state_stall; + } else + ep->ep0.state = ep0_state_token; } static int ast_vhub_ep0_queue(struct usb_ep* u_ep, struct usb_request *u_req, @@ -367,7 +380,7 @@ static int ast_vhub_ep0_queue(struct usb_ep* u_ep, struct usb_request *u_req, return -EINVAL; /* Disabled device */ - if (ep->dev && (!ep->dev->enabled || ep->dev->suspended)) + if (ep->dev && !ep->dev->enabled) return -ESHUTDOWN; /* Data, no buffer and not internal ? */ @@ -390,8 +403,12 @@ static int ast_vhub_ep0_queue(struct usb_ep* u_ep, struct usb_request *u_req, spin_lock_irqsave(&vhub->lock, flags); /* EP0 can only support a single request at a time */ - if (!list_empty(&ep->queue) || ep->ep0.state == ep0_state_token) { + if (!list_empty(&ep->queue) || + ep->ep0.state == ep0_state_token || + ep->ep0.state == ep0_state_stall) { dev_warn(dev, "EP0: Request in wrong state\n"); + EPVDBG(ep, "EP0: list_empty=%d state=%d\n", + list_empty(&ep->queue), ep->ep0.state); spin_unlock_irqrestore(&vhub->lock, flags); return -EBUSY; } @@ -459,6 +476,15 @@ static const struct usb_ep_ops ast_vhub_ep0_ops = { .free_request = ast_vhub_free_request, }; +void ast_vhub_reset_ep0(struct ast_vhub_dev *dev) +{ + struct ast_vhub_ep *ep = &dev->ep0; + + ast_vhub_nuke(ep, -EIO); + ep->ep0.state = ep0_state_token; +} + + void ast_vhub_init_ep0(struct ast_vhub *vhub, struct ast_vhub_ep *ep, struct ast_vhub_dev *dev) { |
