mirror of
https://github.com/torvalds/linux.git
synced 2024-12-28 22:02:28 +00:00
4c55a15774
Now that the SPDX tag is in all USB files, that identifies the license in a specific and legally-defined manner. So the extra GPL text wording can be removed as it is no longer needed at all. This is done on a quest to remove the 700+ different ways that files in the kernel describe the GPL license text. And there's unneeded stuff like the address (sometimes incorrect) for the FSF which is never needed. No copyright headers or other non-license-description text was removed. Acked-by: Chunfeng Yun <chunfeng.yun@mediatek.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
890 lines
20 KiB
C
890 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* mtu3_gadget_ep0.c - MediaTek USB3 DRD peripheral driver ep0 handling
|
|
*
|
|
* Copyright (c) 2016 MediaTek Inc.
|
|
*
|
|
* Author: Chunfeng.Yun <chunfeng.yun@mediatek.com>
|
|
*/
|
|
|
|
#include <linux/usb/composite.h>
|
|
|
|
#include "mtu3.h"
|
|
|
|
/* ep0 is always mtu3->in_eps[0] */
|
|
#define next_ep0_request(mtu) next_request((mtu)->ep0)
|
|
|
|
/* for high speed test mode; see USB 2.0 spec 7.1.20 */
|
|
static const u8 mtu3_test_packet[53] = {
|
|
/* implicit SYNC then DATA0 to start */
|
|
|
|
/* JKJKJKJK x9 */
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* JJKKJJKK x8 */
|
|
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
|
/* JJJJKKKK x8 */
|
|
0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
|
|
/* JJJJJJJKKKKKKK x8 */
|
|
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
/* JJJJJJJK x8 */
|
|
0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd,
|
|
/* JKKKKKKK x10, JK */
|
|
0xfc, 0x7e, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0x7e,
|
|
/* implicit CRC16 then EOP to end */
|
|
};
|
|
|
|
static char *decode_ep0_state(struct mtu3 *mtu)
|
|
{
|
|
switch (mtu->ep0_state) {
|
|
case MU3D_EP0_STATE_SETUP:
|
|
return "SETUP";
|
|
case MU3D_EP0_STATE_TX:
|
|
return "IN";
|
|
case MU3D_EP0_STATE_RX:
|
|
return "OUT";
|
|
case MU3D_EP0_STATE_TX_END:
|
|
return "TX-END";
|
|
case MU3D_EP0_STATE_STALL:
|
|
return "STALL";
|
|
default:
|
|
return "??";
|
|
}
|
|
}
|
|
|
|
static void ep0_req_giveback(struct mtu3 *mtu, struct usb_request *req)
|
|
{
|
|
mtu3_req_complete(mtu->ep0, req, 0);
|
|
}
|
|
|
|
static int
|
|
forward_to_driver(struct mtu3 *mtu, const struct usb_ctrlrequest *setup)
|
|
__releases(mtu->lock)
|
|
__acquires(mtu->lock)
|
|
{
|
|
int ret;
|
|
|
|
if (!mtu->gadget_driver)
|
|
return -EOPNOTSUPP;
|
|
|
|
spin_unlock(&mtu->lock);
|
|
ret = mtu->gadget_driver->setup(&mtu->g, setup);
|
|
spin_lock(&mtu->lock);
|
|
|
|
dev_dbg(mtu->dev, "%s ret %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static void ep0_write_fifo(struct mtu3_ep *mep, const u8 *src, u16 len)
|
|
{
|
|
void __iomem *fifo = mep->mtu->mac_base + U3D_FIFO0;
|
|
u16 index = 0;
|
|
|
|
dev_dbg(mep->mtu->dev, "%s: ep%din, len=%d, buf=%p\n",
|
|
__func__, mep->epnum, len, src);
|
|
|
|
if (len >= 4) {
|
|
iowrite32_rep(fifo, src, len >> 2);
|
|
index = len & ~0x03;
|
|
}
|
|
if (len & 0x02) {
|
|
writew(*(u16 *)&src[index], fifo);
|
|
index += 2;
|
|
}
|
|
if (len & 0x01)
|
|
writeb(src[index], fifo);
|
|
}
|
|
|
|
static void ep0_read_fifo(struct mtu3_ep *mep, u8 *dst, u16 len)
|
|
{
|
|
void __iomem *fifo = mep->mtu->mac_base + U3D_FIFO0;
|
|
u32 value;
|
|
u16 index = 0;
|
|
|
|
dev_dbg(mep->mtu->dev, "%s: ep%dout len=%d buf=%p\n",
|
|
__func__, mep->epnum, len, dst);
|
|
|
|
if (len >= 4) {
|
|
ioread32_rep(fifo, dst, len >> 2);
|
|
index = len & ~0x03;
|
|
}
|
|
if (len & 0x3) {
|
|
value = readl(fifo);
|
|
memcpy(&dst[index], &value, len & 0x3);
|
|
}
|
|
|
|
}
|
|
|
|
static void ep0_load_test_packet(struct mtu3 *mtu)
|
|
{
|
|
/*
|
|
* because the length of test packet is less than max packet of HS ep0,
|
|
* write it into fifo directly.
|
|
*/
|
|
ep0_write_fifo(mtu->ep0, mtu3_test_packet, sizeof(mtu3_test_packet));
|
|
}
|
|
|
|
/*
|
|
* A. send STALL for setup transfer without data stage:
|
|
* set SENDSTALL and SETUPPKTRDY at the same time;
|
|
* B. send STALL for other cases:
|
|
* set SENDSTALL only.
|
|
*/
|
|
static void ep0_stall_set(struct mtu3_ep *mep0, bool set, u32 pktrdy)
|
|
{
|
|
struct mtu3 *mtu = mep0->mtu;
|
|
void __iomem *mbase = mtu->mac_base;
|
|
u32 csr;
|
|
|
|
/* EP0_SENTSTALL is W1C */
|
|
csr = mtu3_readl(mbase, U3D_EP0CSR) & EP0_W1C_BITS;
|
|
if (set)
|
|
csr |= EP0_SENDSTALL | pktrdy;
|
|
else
|
|
csr = (csr & ~EP0_SENDSTALL) | EP0_SENTSTALL;
|
|
mtu3_writel(mtu->mac_base, U3D_EP0CSR, csr);
|
|
|
|
mtu->delayed_status = false;
|
|
mtu->ep0_state = MU3D_EP0_STATE_SETUP;
|
|
|
|
dev_dbg(mtu->dev, "ep0: %s STALL, ep0_state: %s\n",
|
|
set ? "SEND" : "CLEAR", decode_ep0_state(mtu));
|
|
}
|
|
|
|
static int ep0_queue(struct mtu3_ep *mep0, struct mtu3_request *mreq);
|
|
|
|
static void ep0_dummy_complete(struct usb_ep *ep, struct usb_request *req)
|
|
{}
|
|
|
|
static void ep0_set_sel_complete(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
struct mtu3_request *mreq;
|
|
struct mtu3 *mtu;
|
|
struct usb_set_sel_req sel;
|
|
|
|
memcpy(&sel, req->buf, sizeof(sel));
|
|
|
|
mreq = to_mtu3_request(req);
|
|
mtu = mreq->mtu;
|
|
dev_dbg(mtu->dev, "u1sel:%d, u1pel:%d, u2sel:%d, u2pel:%d\n",
|
|
sel.u1_sel, sel.u1_pel, sel.u2_sel, sel.u2_pel);
|
|
}
|
|
|
|
/* queue data stage to handle 6 byte SET_SEL request */
|
|
static int ep0_set_sel(struct mtu3 *mtu, struct usb_ctrlrequest *setup)
|
|
{
|
|
int ret;
|
|
u16 length = le16_to_cpu(setup->wLength);
|
|
|
|
if (unlikely(length != 6)) {
|
|
dev_err(mtu->dev, "%s wrong wLength:%d\n",
|
|
__func__, length);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mtu->ep0_req.mep = mtu->ep0;
|
|
mtu->ep0_req.request.length = 6;
|
|
mtu->ep0_req.request.buf = mtu->setup_buf;
|
|
mtu->ep0_req.request.complete = ep0_set_sel_complete;
|
|
ret = ep0_queue(mtu->ep0, &mtu->ep0_req);
|
|
|
|
return ret < 0 ? ret : 1;
|
|
}
|
|
|
|
static int
|
|
ep0_get_status(struct mtu3 *mtu, const struct usb_ctrlrequest *setup)
|
|
{
|
|
struct mtu3_ep *mep = NULL;
|
|
int handled = 1;
|
|
u8 result[2] = {0, 0};
|
|
u8 epnum = 0;
|
|
int is_in;
|
|
|
|
switch (setup->bRequestType & USB_RECIP_MASK) {
|
|
case USB_RECIP_DEVICE:
|
|
result[0] = mtu->is_self_powered << USB_DEVICE_SELF_POWERED;
|
|
result[0] |= mtu->may_wakeup << USB_DEVICE_REMOTE_WAKEUP;
|
|
|
|
if (mtu->g.speed >= USB_SPEED_SUPER) {
|
|
result[0] |= mtu->u1_enable << USB_DEV_STAT_U1_ENABLED;
|
|
result[0] |= mtu->u2_enable << USB_DEV_STAT_U2_ENABLED;
|
|
}
|
|
|
|
dev_dbg(mtu->dev, "%s result=%x, U1=%x, U2=%x\n", __func__,
|
|
result[0], mtu->u1_enable, mtu->u2_enable);
|
|
|
|
break;
|
|
case USB_RECIP_INTERFACE:
|
|
break;
|
|
case USB_RECIP_ENDPOINT:
|
|
epnum = (u8) le16_to_cpu(setup->wIndex);
|
|
is_in = epnum & USB_DIR_IN;
|
|
epnum &= USB_ENDPOINT_NUMBER_MASK;
|
|
|
|
if (epnum >= mtu->num_eps) {
|
|
handled = -EINVAL;
|
|
break;
|
|
}
|
|
if (!epnum)
|
|
break;
|
|
|
|
mep = (is_in ? mtu->in_eps : mtu->out_eps) + epnum;
|
|
if (!mep->desc) {
|
|
handled = -EINVAL;
|
|
break;
|
|
}
|
|
if (mep->flags & MTU3_EP_STALL)
|
|
result[0] |= 1 << USB_ENDPOINT_HALT;
|
|
|
|
break;
|
|
default:
|
|
/* class, vendor, etc ... delegate */
|
|
handled = 0;
|
|
break;
|
|
}
|
|
|
|
if (handled > 0) {
|
|
int ret;
|
|
|
|
/* prepare a data stage for GET_STATUS */
|
|
dev_dbg(mtu->dev, "get_status=%x\n", *(u16 *)result);
|
|
memcpy(mtu->setup_buf, result, sizeof(result));
|
|
mtu->ep0_req.mep = mtu->ep0;
|
|
mtu->ep0_req.request.length = 2;
|
|
mtu->ep0_req.request.buf = &mtu->setup_buf;
|
|
mtu->ep0_req.request.complete = ep0_dummy_complete;
|
|
ret = ep0_queue(mtu->ep0, &mtu->ep0_req);
|
|
if (ret < 0)
|
|
handled = ret;
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
static int handle_test_mode(struct mtu3 *mtu, struct usb_ctrlrequest *setup)
|
|
{
|
|
void __iomem *mbase = mtu->mac_base;
|
|
int handled = 1;
|
|
|
|
switch (le16_to_cpu(setup->wIndex) >> 8) {
|
|
case TEST_J:
|
|
dev_dbg(mtu->dev, "TEST_J\n");
|
|
mtu->test_mode_nr = TEST_J_MODE;
|
|
break;
|
|
case TEST_K:
|
|
dev_dbg(mtu->dev, "TEST_K\n");
|
|
mtu->test_mode_nr = TEST_K_MODE;
|
|
break;
|
|
case TEST_SE0_NAK:
|
|
dev_dbg(mtu->dev, "TEST_SE0_NAK\n");
|
|
mtu->test_mode_nr = TEST_SE0_NAK_MODE;
|
|
break;
|
|
case TEST_PACKET:
|
|
dev_dbg(mtu->dev, "TEST_PACKET\n");
|
|
mtu->test_mode_nr = TEST_PACKET_MODE;
|
|
break;
|
|
default:
|
|
handled = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
mtu->test_mode = true;
|
|
|
|
/* no TX completion interrupt, and need restart platform after test */
|
|
if (mtu->test_mode_nr == TEST_PACKET_MODE)
|
|
ep0_load_test_packet(mtu);
|
|
|
|
mtu3_writel(mbase, U3D_USB2_TEST_MODE, mtu->test_mode_nr);
|
|
|
|
mtu->ep0_state = MU3D_EP0_STATE_SETUP;
|
|
|
|
out:
|
|
return handled;
|
|
}
|
|
|
|
static int ep0_handle_feature_dev(struct mtu3 *mtu,
|
|
struct usb_ctrlrequest *setup, bool set)
|
|
{
|
|
void __iomem *mbase = mtu->mac_base;
|
|
int handled = -EINVAL;
|
|
u32 lpc;
|
|
|
|
switch (le16_to_cpu(setup->wValue)) {
|
|
case USB_DEVICE_REMOTE_WAKEUP:
|
|
mtu->may_wakeup = !!set;
|
|
handled = 1;
|
|
break;
|
|
case USB_DEVICE_TEST_MODE:
|
|
if (!set || (mtu->g.speed != USB_SPEED_HIGH) ||
|
|
(le16_to_cpu(setup->wIndex) & 0xff))
|
|
break;
|
|
|
|
handled = handle_test_mode(mtu, setup);
|
|
break;
|
|
case USB_DEVICE_U1_ENABLE:
|
|
if (mtu->g.speed < USB_SPEED_SUPER ||
|
|
mtu->g.state != USB_STATE_CONFIGURED)
|
|
break;
|
|
|
|
lpc = mtu3_readl(mbase, U3D_LINK_POWER_CONTROL);
|
|
if (set)
|
|
lpc |= SW_U1_ACCEPT_ENABLE;
|
|
else
|
|
lpc &= ~SW_U1_ACCEPT_ENABLE;
|
|
mtu3_writel(mbase, U3D_LINK_POWER_CONTROL, lpc);
|
|
|
|
mtu->u1_enable = !!set;
|
|
handled = 1;
|
|
break;
|
|
case USB_DEVICE_U2_ENABLE:
|
|
if (mtu->g.speed < USB_SPEED_SUPER ||
|
|
mtu->g.state != USB_STATE_CONFIGURED)
|
|
break;
|
|
|
|
lpc = mtu3_readl(mbase, U3D_LINK_POWER_CONTROL);
|
|
if (set)
|
|
lpc |= SW_U2_ACCEPT_ENABLE;
|
|
else
|
|
lpc &= ~SW_U2_ACCEPT_ENABLE;
|
|
mtu3_writel(mbase, U3D_LINK_POWER_CONTROL, lpc);
|
|
|
|
mtu->u2_enable = !!set;
|
|
handled = 1;
|
|
break;
|
|
default:
|
|
handled = -EINVAL;
|
|
break;
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
static int ep0_handle_feature(struct mtu3 *mtu,
|
|
struct usb_ctrlrequest *setup, bool set)
|
|
{
|
|
struct mtu3_ep *mep;
|
|
int handled = -EINVAL;
|
|
int is_in;
|
|
u16 value;
|
|
u16 index;
|
|
u8 epnum;
|
|
|
|
value = le16_to_cpu(setup->wValue);
|
|
index = le16_to_cpu(setup->wIndex);
|
|
|
|
switch (setup->bRequestType & USB_RECIP_MASK) {
|
|
case USB_RECIP_DEVICE:
|
|
handled = ep0_handle_feature_dev(mtu, setup, set);
|
|
break;
|
|
case USB_RECIP_INTERFACE:
|
|
/* superspeed only */
|
|
if (value == USB_INTRF_FUNC_SUSPEND &&
|
|
mtu->g.speed >= USB_SPEED_SUPER) {
|
|
/*
|
|
* forward the request because function drivers
|
|
* should handle it
|
|
*/
|
|
handled = 0;
|
|
}
|
|
break;
|
|
case USB_RECIP_ENDPOINT:
|
|
epnum = index & USB_ENDPOINT_NUMBER_MASK;
|
|
if (epnum == 0 || epnum >= mtu->num_eps ||
|
|
value != USB_ENDPOINT_HALT)
|
|
break;
|
|
|
|
is_in = index & USB_DIR_IN;
|
|
mep = (is_in ? mtu->in_eps : mtu->out_eps) + epnum;
|
|
if (!mep->desc)
|
|
break;
|
|
|
|
handled = 1;
|
|
/* ignore request if endpoint is wedged */
|
|
if (mep->wedged)
|
|
break;
|
|
|
|
mtu3_ep_stall_set(mep, set);
|
|
break;
|
|
default:
|
|
/* class, vendor, etc ... delegate */
|
|
handled = 0;
|
|
break;
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
/*
|
|
* handle all control requests can be handled
|
|
* returns:
|
|
* negative errno - error happened
|
|
* zero - need delegate SETUP to gadget driver
|
|
* positive - already handled
|
|
*/
|
|
static int handle_standard_request(struct mtu3 *mtu,
|
|
struct usb_ctrlrequest *setup)
|
|
{
|
|
void __iomem *mbase = mtu->mac_base;
|
|
enum usb_device_state state = mtu->g.state;
|
|
int handled = -EINVAL;
|
|
u32 dev_conf;
|
|
u16 value;
|
|
|
|
value = le16_to_cpu(setup->wValue);
|
|
|
|
/* the gadget driver handles everything except what we must handle */
|
|
switch (setup->bRequest) {
|
|
case USB_REQ_SET_ADDRESS:
|
|
/* change it after the status stage */
|
|
mtu->address = (u8) (value & 0x7f);
|
|
dev_dbg(mtu->dev, "set address to 0x%x\n", mtu->address);
|
|
|
|
dev_conf = mtu3_readl(mbase, U3D_DEVICE_CONF);
|
|
dev_conf &= ~DEV_ADDR_MSK;
|
|
dev_conf |= DEV_ADDR(mtu->address);
|
|
mtu3_writel(mbase, U3D_DEVICE_CONF, dev_conf);
|
|
|
|
if (mtu->address)
|
|
usb_gadget_set_state(&mtu->g, USB_STATE_ADDRESS);
|
|
else
|
|
usb_gadget_set_state(&mtu->g, USB_STATE_DEFAULT);
|
|
|
|
handled = 1;
|
|
break;
|
|
case USB_REQ_SET_CONFIGURATION:
|
|
if (state == USB_STATE_ADDRESS) {
|
|
usb_gadget_set_state(&mtu->g,
|
|
USB_STATE_CONFIGURED);
|
|
} else if (state == USB_STATE_CONFIGURED) {
|
|
/*
|
|
* USB2 spec sec 9.4.7, if wValue is 0 then dev
|
|
* is moved to addressed state
|
|
*/
|
|
if (!value)
|
|
usb_gadget_set_state(&mtu->g,
|
|
USB_STATE_ADDRESS);
|
|
}
|
|
handled = 0;
|
|
break;
|
|
case USB_REQ_CLEAR_FEATURE:
|
|
handled = ep0_handle_feature(mtu, setup, 0);
|
|
break;
|
|
case USB_REQ_SET_FEATURE:
|
|
handled = ep0_handle_feature(mtu, setup, 1);
|
|
break;
|
|
case USB_REQ_GET_STATUS:
|
|
handled = ep0_get_status(mtu, setup);
|
|
break;
|
|
case USB_REQ_SET_SEL:
|
|
handled = ep0_set_sel(mtu, setup);
|
|
break;
|
|
case USB_REQ_SET_ISOCH_DELAY:
|
|
handled = 1;
|
|
break;
|
|
default:
|
|
/* delegate SET_CONFIGURATION, etc */
|
|
handled = 0;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
/* receive an data packet (OUT) */
|
|
static void ep0_rx_state(struct mtu3 *mtu)
|
|
{
|
|
struct mtu3_request *mreq;
|
|
struct usb_request *req;
|
|
void __iomem *mbase = mtu->mac_base;
|
|
u32 maxp;
|
|
u32 csr;
|
|
u16 count = 0;
|
|
|
|
dev_dbg(mtu->dev, "%s\n", __func__);
|
|
|
|
csr = mtu3_readl(mbase, U3D_EP0CSR) & EP0_W1C_BITS;
|
|
mreq = next_ep0_request(mtu);
|
|
req = &mreq->request;
|
|
|
|
/* read packet and ack; or stall because of gadget driver bug */
|
|
if (req) {
|
|
void *buf = req->buf + req->actual;
|
|
unsigned int len = req->length - req->actual;
|
|
|
|
/* read the buffer */
|
|
count = mtu3_readl(mbase, U3D_RXCOUNT0);
|
|
if (count > len) {
|
|
req->status = -EOVERFLOW;
|
|
count = len;
|
|
}
|
|
ep0_read_fifo(mtu->ep0, buf, count);
|
|
req->actual += count;
|
|
csr |= EP0_RXPKTRDY;
|
|
|
|
maxp = mtu->g.ep0->maxpacket;
|
|
if (count < maxp || req->actual == req->length) {
|
|
mtu->ep0_state = MU3D_EP0_STATE_SETUP;
|
|
dev_dbg(mtu->dev, "ep0 state: %s\n",
|
|
decode_ep0_state(mtu));
|
|
|
|
csr |= EP0_DATAEND;
|
|
} else {
|
|
req = NULL;
|
|
}
|
|
} else {
|
|
csr |= EP0_RXPKTRDY | EP0_SENDSTALL;
|
|
dev_dbg(mtu->dev, "%s: SENDSTALL\n", __func__);
|
|
}
|
|
|
|
mtu3_writel(mbase, U3D_EP0CSR, csr);
|
|
|
|
/* give back the request if have received all data */
|
|
if (req)
|
|
ep0_req_giveback(mtu, req);
|
|
|
|
}
|
|
|
|
/* transmitting to the host (IN) */
|
|
static void ep0_tx_state(struct mtu3 *mtu)
|
|
{
|
|
struct mtu3_request *mreq = next_ep0_request(mtu);
|
|
struct usb_request *req;
|
|
u32 csr;
|
|
u8 *src;
|
|
u8 count;
|
|
u32 maxp;
|
|
|
|
dev_dbg(mtu->dev, "%s\n", __func__);
|
|
|
|
if (!mreq)
|
|
return;
|
|
|
|
maxp = mtu->g.ep0->maxpacket;
|
|
req = &mreq->request;
|
|
|
|
/* load the data */
|
|
src = (u8 *)req->buf + req->actual;
|
|
count = min(maxp, req->length - req->actual);
|
|
if (count)
|
|
ep0_write_fifo(mtu->ep0, src, count);
|
|
|
|
dev_dbg(mtu->dev, "%s act=%d, len=%d, cnt=%d, maxp=%d zero=%d\n",
|
|
__func__, req->actual, req->length, count, maxp, req->zero);
|
|
|
|
req->actual += count;
|
|
|
|
if ((count < maxp)
|
|
|| ((req->actual == req->length) && !req->zero))
|
|
mtu->ep0_state = MU3D_EP0_STATE_TX_END;
|
|
|
|
/* send it out, triggering a "txpktrdy cleared" irq */
|
|
csr = mtu3_readl(mtu->mac_base, U3D_EP0CSR) & EP0_W1C_BITS;
|
|
mtu3_writel(mtu->mac_base, U3D_EP0CSR, csr | EP0_TXPKTRDY);
|
|
|
|
dev_dbg(mtu->dev, "%s ep0csr=0x%x\n", __func__,
|
|
mtu3_readl(mtu->mac_base, U3D_EP0CSR));
|
|
}
|
|
|
|
static void ep0_read_setup(struct mtu3 *mtu, struct usb_ctrlrequest *setup)
|
|
{
|
|
struct mtu3_request *mreq;
|
|
u32 count;
|
|
u32 csr;
|
|
|
|
csr = mtu3_readl(mtu->mac_base, U3D_EP0CSR) & EP0_W1C_BITS;
|
|
count = mtu3_readl(mtu->mac_base, U3D_RXCOUNT0);
|
|
|
|
ep0_read_fifo(mtu->ep0, (u8 *)setup, count);
|
|
|
|
dev_dbg(mtu->dev, "SETUP req%02x.%02x v%04x i%04x l%04x\n",
|
|
setup->bRequestType, setup->bRequest,
|
|
le16_to_cpu(setup->wValue), le16_to_cpu(setup->wIndex),
|
|
le16_to_cpu(setup->wLength));
|
|
|
|
/* clean up any leftover transfers */
|
|
mreq = next_ep0_request(mtu);
|
|
if (mreq)
|
|
ep0_req_giveback(mtu, &mreq->request);
|
|
|
|
if (le16_to_cpu(setup->wLength) == 0) {
|
|
; /* no data stage, nothing to do */
|
|
} else if (setup->bRequestType & USB_DIR_IN) {
|
|
mtu3_writel(mtu->mac_base, U3D_EP0CSR,
|
|
csr | EP0_SETUPPKTRDY | EP0_DPHTX);
|
|
mtu->ep0_state = MU3D_EP0_STATE_TX;
|
|
} else {
|
|
mtu3_writel(mtu->mac_base, U3D_EP0CSR,
|
|
(csr | EP0_SETUPPKTRDY) & (~EP0_DPHTX));
|
|
mtu->ep0_state = MU3D_EP0_STATE_RX;
|
|
}
|
|
}
|
|
|
|
static int ep0_handle_setup(struct mtu3 *mtu)
|
|
__releases(mtu->lock)
|
|
__acquires(mtu->lock)
|
|
{
|
|
struct usb_ctrlrequest setup;
|
|
struct mtu3_request *mreq;
|
|
void __iomem *mbase = mtu->mac_base;
|
|
int handled = 0;
|
|
|
|
ep0_read_setup(mtu, &setup);
|
|
|
|
if ((setup.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
|
|
handled = handle_standard_request(mtu, &setup);
|
|
|
|
dev_dbg(mtu->dev, "handled %d, ep0_state: %s\n",
|
|
handled, decode_ep0_state(mtu));
|
|
|
|
if (handled < 0)
|
|
goto stall;
|
|
else if (handled > 0)
|
|
goto finish;
|
|
|
|
handled = forward_to_driver(mtu, &setup);
|
|
if (handled < 0) {
|
|
stall:
|
|
dev_dbg(mtu->dev, "%s stall (%d)\n", __func__, handled);
|
|
|
|
ep0_stall_set(mtu->ep0, true,
|
|
le16_to_cpu(setup.wLength) ? 0 : EP0_SETUPPKTRDY);
|
|
|
|
return 0;
|
|
}
|
|
|
|
finish:
|
|
if (mtu->test_mode) {
|
|
; /* nothing to do */
|
|
} else if (handled == USB_GADGET_DELAYED_STATUS) {
|
|
/* handle the delay STATUS phase till receive ep_queue on ep0 */
|
|
mtu->delayed_status = true;
|
|
} else if (le16_to_cpu(setup.wLength) == 0) { /* no data stage */
|
|
|
|
mtu3_writel(mbase, U3D_EP0CSR,
|
|
(mtu3_readl(mbase, U3D_EP0CSR) & EP0_W1C_BITS)
|
|
| EP0_SETUPPKTRDY | EP0_DATAEND);
|
|
|
|
/* complete zlp request directly */
|
|
mreq = next_ep0_request(mtu);
|
|
if (mreq && !mreq->request.length)
|
|
ep0_req_giveback(mtu, &mreq->request);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
irqreturn_t mtu3_ep0_isr(struct mtu3 *mtu)
|
|
{
|
|
void __iomem *mbase = mtu->mac_base;
|
|
struct mtu3_request *mreq;
|
|
u32 int_status;
|
|
irqreturn_t ret = IRQ_NONE;
|
|
u32 csr;
|
|
u32 len;
|
|
|
|
int_status = mtu3_readl(mbase, U3D_EPISR);
|
|
int_status &= mtu3_readl(mbase, U3D_EPIER);
|
|
mtu3_writel(mbase, U3D_EPISR, int_status); /* W1C */
|
|
|
|
/* only handle ep0's */
|
|
if (!(int_status & EP0ISR))
|
|
return IRQ_NONE;
|
|
|
|
csr = mtu3_readl(mbase, U3D_EP0CSR);
|
|
|
|
dev_dbg(mtu->dev, "%s csr=0x%x\n", __func__, csr);
|
|
|
|
/* we sent a stall.. need to clear it now.. */
|
|
if (csr & EP0_SENTSTALL) {
|
|
ep0_stall_set(mtu->ep0, false, 0);
|
|
csr = mtu3_readl(mbase, U3D_EP0CSR);
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
dev_dbg(mtu->dev, "ep0_state: %s\n", decode_ep0_state(mtu));
|
|
|
|
switch (mtu->ep0_state) {
|
|
case MU3D_EP0_STATE_TX:
|
|
/* irq on clearing txpktrdy */
|
|
if ((csr & EP0_FIFOFULL) == 0) {
|
|
ep0_tx_state(mtu);
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
break;
|
|
case MU3D_EP0_STATE_RX:
|
|
/* irq on set rxpktrdy */
|
|
if (csr & EP0_RXPKTRDY) {
|
|
ep0_rx_state(mtu);
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
break;
|
|
case MU3D_EP0_STATE_TX_END:
|
|
mtu3_writel(mbase, U3D_EP0CSR,
|
|
(csr & EP0_W1C_BITS) | EP0_DATAEND);
|
|
|
|
mreq = next_ep0_request(mtu);
|
|
if (mreq)
|
|
ep0_req_giveback(mtu, &mreq->request);
|
|
|
|
mtu->ep0_state = MU3D_EP0_STATE_SETUP;
|
|
ret = IRQ_HANDLED;
|
|
dev_dbg(mtu->dev, "ep0_state: %s\n", decode_ep0_state(mtu));
|
|
break;
|
|
case MU3D_EP0_STATE_SETUP:
|
|
if (!(csr & EP0_SETUPPKTRDY))
|
|
break;
|
|
|
|
len = mtu3_readl(mbase, U3D_RXCOUNT0);
|
|
if (len != 8) {
|
|
dev_err(mtu->dev, "SETUP packet len %d != 8 ?\n", len);
|
|
break;
|
|
}
|
|
|
|
ep0_handle_setup(mtu);
|
|
ret = IRQ_HANDLED;
|
|
break;
|
|
default:
|
|
/* can't happen */
|
|
ep0_stall_set(mtu->ep0, true, 0);
|
|
WARN_ON(1);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int mtu3_ep0_enable(struct usb_ep *ep,
|
|
const struct usb_endpoint_descriptor *desc)
|
|
{
|
|
/* always enabled */
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int mtu3_ep0_disable(struct usb_ep *ep)
|
|
{
|
|
/* always enabled */
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int ep0_queue(struct mtu3_ep *mep, struct mtu3_request *mreq)
|
|
{
|
|
struct mtu3 *mtu = mep->mtu;
|
|
|
|
mreq->mtu = mtu;
|
|
mreq->request.actual = 0;
|
|
mreq->request.status = -EINPROGRESS;
|
|
|
|
dev_dbg(mtu->dev, "%s %s (ep0_state: %s), len#%d\n", __func__,
|
|
mep->name, decode_ep0_state(mtu), mreq->request.length);
|
|
|
|
switch (mtu->ep0_state) {
|
|
case MU3D_EP0_STATE_SETUP:
|
|
case MU3D_EP0_STATE_RX: /* control-OUT data */
|
|
case MU3D_EP0_STATE_TX: /* control-IN data */
|
|
break;
|
|
default:
|
|
dev_err(mtu->dev, "%s, error in ep0 state %s\n", __func__,
|
|
decode_ep0_state(mtu));
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mtu->delayed_status) {
|
|
u32 csr;
|
|
|
|
mtu->delayed_status = false;
|
|
csr = mtu3_readl(mtu->mac_base, U3D_EP0CSR) & EP0_W1C_BITS;
|
|
csr |= EP0_SETUPPKTRDY | EP0_DATAEND;
|
|
mtu3_writel(mtu->mac_base, U3D_EP0CSR, csr);
|
|
/* needn't giveback the request for handling delay STATUS */
|
|
return 0;
|
|
}
|
|
|
|
if (!list_empty(&mep->req_list))
|
|
return -EBUSY;
|
|
|
|
list_add_tail(&mreq->list, &mep->req_list);
|
|
|
|
/* sequence #1, IN ... start writing the data */
|
|
if (mtu->ep0_state == MU3D_EP0_STATE_TX)
|
|
ep0_tx_state(mtu);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mtu3_ep0_queue(struct usb_ep *ep,
|
|
struct usb_request *req, gfp_t gfp)
|
|
{
|
|
struct mtu3_ep *mep;
|
|
struct mtu3_request *mreq;
|
|
struct mtu3 *mtu;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
if (!ep || !req)
|
|
return -EINVAL;
|
|
|
|
mep = to_mtu3_ep(ep);
|
|
mtu = mep->mtu;
|
|
mreq = to_mtu3_request(req);
|
|
|
|
spin_lock_irqsave(&mtu->lock, flags);
|
|
ret = ep0_queue(mep, mreq);
|
|
spin_unlock_irqrestore(&mtu->lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
static int mtu3_ep0_dequeue(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
/* we just won't support this */
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int mtu3_ep0_halt(struct usb_ep *ep, int value)
|
|
{
|
|
struct mtu3_ep *mep;
|
|
struct mtu3 *mtu;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
if (!ep || !value)
|
|
return -EINVAL;
|
|
|
|
mep = to_mtu3_ep(ep);
|
|
mtu = mep->mtu;
|
|
|
|
dev_dbg(mtu->dev, "%s\n", __func__);
|
|
|
|
spin_lock_irqsave(&mtu->lock, flags);
|
|
|
|
if (!list_empty(&mep->req_list)) {
|
|
ret = -EBUSY;
|
|
goto cleanup;
|
|
}
|
|
|
|
switch (mtu->ep0_state) {
|
|
/*
|
|
* stalls are usually issued after parsing SETUP packet, either
|
|
* directly in irq context from setup() or else later.
|
|
*/
|
|
case MU3D_EP0_STATE_TX:
|
|
case MU3D_EP0_STATE_TX_END:
|
|
case MU3D_EP0_STATE_RX:
|
|
case MU3D_EP0_STATE_SETUP:
|
|
ep0_stall_set(mtu->ep0, true, 0);
|
|
break;
|
|
default:
|
|
dev_dbg(mtu->dev, "ep0 can't halt in state %s\n",
|
|
decode_ep0_state(mtu));
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
cleanup:
|
|
spin_unlock_irqrestore(&mtu->lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
const struct usb_ep_ops mtu3_ep0_ops = {
|
|
.enable = mtu3_ep0_enable,
|
|
.disable = mtu3_ep0_disable,
|
|
.alloc_request = mtu3_alloc_request,
|
|
.free_request = mtu3_free_request,
|
|
.queue = mtu3_ep0_queue,
|
|
.dequeue = mtu3_ep0_dequeue,
|
|
.set_halt = mtu3_ep0_halt,
|
|
};
|