// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015 MediaTek Inc.
* Author:
* Zhigang.Wei <zhigang.wei@mediatek.com>
* Chunfeng.Yun <chunfeng.yun@mediatek.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "xhci.h"
#include "xhci-mtk.h"
#define SSP_BW_BOUNDARY 130000
#define SS_BW_BOUNDARY 51000
/* table 5-5. High-speed Isoc Transaction Limits in usb_20 spec */
#define HS_BW_BOUNDARY 6144
/* usb2 spec section11.18.1: at most 188 FS bytes per microframe */
#define FS_PAYLOAD_MAX 188
#define LS_PAYLOAD_MAX 18
/* section 11.18.1, per fs frame */
#define FS_BW_BOUNDARY 1157
#define LS_BW_BOUNDARY 144
/*
* max number of microframes for split transfer, assume extra-cs budget is 0
* for fs isoc in : 1 ss + 1 idle + 6 cs (roundup(1023/188))
*/
#define TT_MICROFRAMES_MAX 8
/* offset from SS for fs/ls isoc/intr ep (ss + idle) */
#define CS_OFFSET 2
#define DBG_BUF_EN 64
/* schedule error type */
#define ESCH_SS_Y6 1001
#define ESCH_SS_OVERLAP 1002
#define ESCH_CS_OVERFLOW 1003
#define ESCH_BW_OVERFLOW 1004
#define ESCH_FIXME 1005
/* mtk scheduler bitmasks */
#define EP_BPKTS(p) ((p) & 0x7f)
#define EP_BCSCOUNT(p) (((p) & 0x7) << 8)
#define EP_BBM(p) ((p) << 11)
#define EP_BOFFSET(p) ((p) & 0x3fff)
#define EP_BREPEAT(p) (((p) & 0x7fff) << 16)
static char *sch_error_string(int err_num)
{
switch (err_num) {
case ESCH_SS_Y6:
return "Can't schedule Start-Split in Y6";
case ESCH_SS_OVERLAP:
return "Can't find a suitable Start-Split location";
case ESCH_CS_OVERFLOW:
return "The last Complete-Split is greater than 7";
case ESCH_BW_OVERFLOW:
return "Bandwidth exceeds the maximum limit";
case ESCH_FIXME:
return "FIXME, to be resolved";
default:
return "Unknown";
}
}
static int is_fs_or_ls(enum usb_device_speed speed)
{
return speed == USB_SPEED_FULL || speed == USB_SPEED_LOW;
}
static const char *
decode_ep(struct usb_host_endpoint *ep, enum usb_device_speed speed)
{
static char buf[DBG_BUF_EN];
struct usb_endpoint_descriptor *epd = &ep->desc;
unsigned int interval;
const char *unit;
interval = usb_decode_interval(epd, speed);
if (interval % 1000) {
unit = "us";
} else {
unit = "ms";
interval /= 1000;
}
snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s",
usb_speed_string(speed), usb_endpoint_num(epd),
usb_endpoint_dir_in(epd) ? "in" : "out",
usb_ep_type_string(usb_endpoint_type(epd)),
usb_endpoint_maxp(epd), epd->bInterval, interval, unit);
return buf;
}
static u32 get_bw_boundary(enum usb_device_speed speed)
{
u32 boundary;
switch (speed) {
case USB_SPEED_SUPER_PLUS:
boundary = SSP_BW_BOUNDARY;
break;
case USB_SPEED_SUPER:
boundary = SS_BW_BOUNDARY;
break;
default:
boundary = HS_BW_BOUNDARY;
break;
}
return boundary;
}
/*
* get the bandwidth domain which @ep belongs to.
*
* the bandwidth domain array is saved to @sch_array of struct xhci_hcd_mtk,
* each HS root port is treated as a single bandwidth domain,
* but each SS root port is treated as two bandwidth domains, one for IN eps,
* one for OUT eps.
*/
static struct mu3h_sch_bw_info *
get_bw_info(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
struct usb_host_endpoint *ep)
{
struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd);
struct xhci_virt_device *virt_dev;
int bw_index;
virt_dev = xhci->devs[udev->slot_id];
if (!virt_dev->rhub_port) {
WARN_ONCE(1, "%s invalid rhub port\n", dev_name(&udev->dev));
return NULL;
}
if (udev->speed >= USB_SPEED_SUPER) {
if (usb_endpoint_dir_out(&ep->desc))
bw_index = (virt_dev->rhub_port->hw_portnum) * 2;
else
bw_index = (virt_dev->rhub_port->hw_portnum) * 2 + 1;
} else {
/* add one more for each SS port */
bw_index = virt_dev->rhub_port->hw_portnum + xhci->usb3_rhub.num_ports;
}
return &mtk->sch_array[bw_index];
}
static u32 get_esit(struct xhci_ep_ctx *ep_ctx)
{
u32 esit;
esit = 1 << CTX_TO_EP_INTERVAL(le32_to_cpu(ep_ctx->ep_info));
if (esit > XHCI_MTK_MAX_ESIT)
esit = XHCI_MTK_MAX_ESIT;
return esit;
}
static struct mu3h_sch_tt *find_tt(struct usb_device *udev)
{
struct usb_tt *utt = udev->tt;
struct mu3h_sch_tt *tt, **tt_index, **ptt;
bool allocated_index = false;
if (!utt)
return NULL; /* Not below a TT */
/*
* Find/create our data structure.
* For hubs with a single TT, we get it directly.
* For hubs with multiple TTs, there's an extra level of pointers.
*/
tt_index = NULL;
if (utt->multi) {
tt_index = utt->hcpriv;
if (!tt_index) { /* Create the index array */
tt_index = kcalloc(utt->hub->maxchild,
sizeof(*tt_index), GFP_KERNEL);
if (!tt_index)
return ERR_PTR(-ENOMEM);
utt->hcpriv = tt_index;
allocated_index = true;
}
ptt = &tt_index[udev->ttport - 1];
} else {
ptt = (struct mu3h_sch_tt **) &utt->hcpriv;
}
tt = *ptt;
if (!tt) { /* Create the mu3h_sch_tt */
tt = kzalloc(sizeof(*tt), GFP_KERNEL);
if (!tt) {
if (allocated_index) {
utt->hcpriv = NULL;
kfree(