// 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<