| // 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/iopoll.h> |
| #include <linux/usb/composite.h> |
| |
| #include "mtu3.h" |
| #include "mtu3_debug.h" |
| #include "mtu3_trace.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 void ep0_do_status_stage(struct mtu3 *mtu) |
| { |
| void __iomem *mbase = mtu->mac_base; |
| u32 value; |
| |
| value = mtu3_readl(mbase, U3D_EP0CSR) & EP0_W1C_BITS; |
| mtu3_writel(mbase, U3D_EP0CSR, value | EP0_SETUPPKTRDY | EP0_DATAEND); |
| } |
| |
| 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; |
| u32 value; |
| |
| switch (le16_to_cpu(setup->wIndex) >> 8) { |
| case USB_TEST_J: |
| dev_dbg(mtu->dev, "USB_TEST_J\n"); |
| mtu->test_mode_nr = TEST_J_MODE; |
| break; |
| case USB_TEST_K: |
| dev_dbg(mtu->dev, "USB_TEST_K\n"); |
| mtu->test_mode_nr = TEST_K_MODE; |
| break; |
| case USB_TEST_SE0_NAK: |
| dev_dbg(mtu->dev, "USB_TEST_SE0_NAK\n"); |
| mtu->test_mode_nr = TEST_SE0_NAK_MODE; |
| break; |
| case USB_TEST_PACKET: |
| dev_dbg(mtu->dev, "USB_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); |
| |
| /* send status before entering test mode. */ |
| ep0_do_status_stage(mtu); |
| |
| /* wait for ACK status sent by host */ |
| readl_poll_timeout_atomic(mbase + U3D_EP0CSR, value, |
| !(value & EP0_DATAEND), 100, 5000); |
| |
| 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_REQUEST_ENABLE; |
| else |
| lpc &= ~SW_U1_REQUEST_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_REQUEST_ENABLE; |
| else |
| lpc &= ~SW_U2_REQUEST_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->flags & MTU3_EP_WEDGE) |
| 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; |
| u32 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; |
| int handled = 0; |
| |
| ep0_read_setup(mtu, &setup); |
| trace_mtu3_handle_setup(&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) { |
| |
| mreq = next_ep0_request(mtu); |
| if (mreq) { |
| /* already asked us to continue delayed status */ |
| ep0_do_status_stage(mtu); |
| ep0_req_giveback(mtu, &mreq->request); |
| } else { |
| /* do delayed STATUS stage till receive ep0_queue */ |
| mtu->delayed_status = true; |
| } |
| } else if (le16_to_cpu(setup.wLength) == 0) { /* no data stage */ |
| |
| ep0_do_status_stage(mtu); |
| /* 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 | SETUPENDISR))) |
| return IRQ_NONE; |
| |
| /* abort current SETUP, and process new one */ |
| if (int_status & SETUPENDISR) |
| mtu->ep0_state = MU3D_EP0_STATE_SETUP; |
| |
| 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)); |
| mtu3_dbg_trace(mtu->dev, "ep0_state %s", 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) { |
| |
| mtu->delayed_status = false; |
| ep0_do_status_stage(mtu); |
| /* 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, |
| }; |