aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHoward Chen <howard.chen@linaro.org>2015-03-20 12:20:22 +0800
committerVincent Guittot <vincent.guittot@linaro.org>2015-04-16 11:53:26 +0200
commitb839cdc22f9e7d93b6bb6fb04efb126e42783ee7 (patch)
treeebae557adc6421d6e503825d8c92b2f03f02ecdb
parent4b7ddf619ce912f6a22d5a19d2804a38755b3e39 (diff)
drivers/usb/host/xhci-mtk.h
-rw-r--r--drivers/usb/host/xhci-mtk.c735
-rw-r--r--drivers/usb/host/xhci-mtk.h95
2 files changed, 830 insertions, 0 deletions
diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
new file mode 100644
index 000000000000..ad456f012075
--- /dev/null
+++ b/drivers/usb/host/xhci-mtk.c
@@ -0,0 +1,735 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ * Author: Chunfeng.Yun <chunfeng.yun@mediatek.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include "xhci.h"
+#include "xhci-mtk.h"
+
+
+#define SCH_SUCCESS 1
+#define SCH_FAIL 0
+
+#define MAX_PORT_NUM 4
+#define SS_BW_BOUND 51000
+#define HS_BW_BOUND 6144
+
+/* mtk scheduler bitmasks */
+#define EP_BPKTS(p) ((p) & 0x3f)
+#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 int add_sch_ep(struct sch_ep *sep, struct sch_port *sport)
+{
+ struct sch_ep **ep_array;
+ int speed = sep->speed;
+ int ep_type = sep->ep_type;
+ int i;
+
+ if (sep->is_in && speed == USB_SPEED_SUPER)
+ ep_array = sport->ss_in_eps;
+ else if (speed == USB_SPEED_SUPER)
+ ep_array = sport->ss_out_eps;
+ else if (speed == USB_SPEED_HIGH ||
+ (sep->is_tt && ep_type == USB_ENDPOINT_XFER_ISOC))
+ ep_array = sport->hs_eps;
+ else
+ ep_array = sport->tt_intr_eps;
+
+ for (i = 0; i < MAX_EP_NUM; i++) {
+ if (ep_array[i] == NULL) {
+ ep_array[i] = sep;
+ return SCH_SUCCESS;
+ }
+ }
+ return SCH_FAIL;
+}
+
+
+static int need_more_bw_cost(int old_interval, int old_offset,
+ int new_interval, int new_offset)
+{
+ int tmp_offset;
+ int tmp_interval;
+ int ret = 0;
+
+ if (old_interval >= new_interval) {
+ tmp_offset = old_offset + old_interval - new_offset;
+ tmp_interval = new_interval;
+ } else {
+ tmp_offset = new_offset + new_interval - old_offset;
+ tmp_interval = old_interval;
+ }
+ if (tmp_offset % tmp_interval == 0)
+ ret = 1;
+
+ return ret;
+}
+
+static int get_bw_cost(struct sch_ep *sep, int interval, int offset)
+{
+ int ep_offset;
+ int ep_interval;
+ int ep_repeat;
+ int ep_mult;
+ int m_offset;
+ int k;
+ int bw_cost = 0;
+
+ ep_interval = sep->interval;
+ ep_offset = sep->offset;
+ if (sep->repeat == 0) {
+ if (need_more_bw_cost(ep_interval, ep_offset, interval, offset))
+ bw_cost = sep->bw_cost;
+ } else {
+ ep_repeat = sep->repeat;
+ ep_mult = sep->mult;
+ for (k = 0; k <= ep_mult; k++) {
+ m_offset = ep_offset + (k * ep_repeat);
+ if (need_more_bw_cost(ep_interval, m_offset,
+ interval, offset)) {
+ bw_cost = sep->bw_cost;
+ break;
+ }
+ }
+ }
+ return bw_cost;
+}
+
+
+static int count_ss_bw_single(struct sch_ep **ep_array, int interval,
+ int offset, int td_size)
+{
+ struct sch_ep *cur_sep;
+ int bw_required;
+ int i;
+
+ bw_required = 0;
+ for (i = 0; i < MAX_EP_NUM; i++) {
+ cur_sep = ep_array[i];
+ if (cur_sep == NULL)
+ continue;
+ bw_required += get_bw_cost(cur_sep, interval, offset);
+ }
+ bw_required += td_size;
+ return bw_required;
+}
+
+static int count_ss_bw_repeat(struct sch_ep **ep_array, int maxp,
+ int interval, int burst, int mult, int offset, int repeat)
+{
+ int bw_required_per_repeat;
+ int final_bw_required;
+ int tmp_bw_required;
+ int bw_required[3] = {0, 0, 0};
+ struct sch_ep *cur_sep;
+ int cur_offset;
+ int i, j;
+
+ bw_required_per_repeat = maxp * (burst + 1);
+ for (j = 0; j <= mult; j++) {
+ tmp_bw_required = 0;
+ cur_offset = offset + (j * repeat);
+ for (i = 0; i < MAX_EP_NUM; i++) {
+ cur_sep = ep_array[i];
+ if (cur_sep == NULL)
+ continue;
+ tmp_bw_required +=
+ get_bw_cost(cur_sep, interval, cur_offset);
+ }
+ bw_required[j] = tmp_bw_required;
+ }
+ final_bw_required = SS_BW_BOUND;
+ for (j = 0; j <= mult; j++) {
+ if (bw_required[j] < final_bw_required)
+ final_bw_required = bw_required[j];
+ }
+ final_bw_required += bw_required_per_repeat;
+
+ return final_bw_required;
+}
+
+
+static int count_ss_bw(struct sch_port *sport, struct sch_ep *sep,
+ int offset, int repeat, int td_size)
+{
+ struct sch_ep **ep_array;
+ int interval = sep->interval;
+ int bw_cost;
+
+ ep_array = sep->is_in ? sport->ss_in_eps : sport->ss_out_eps;
+
+ if (repeat) {
+ bw_cost = count_ss_bw_repeat(ep_array, sep->maxp, interval,
+ sep->burst, sep->mult, offset, repeat);
+ } else
+ bw_cost = count_ss_bw_single(ep_array, interval,
+ offset, td_size);
+
+ return bw_cost;
+}
+
+static int is_isoc_tt_mframe_overlap(struct sch_ep *sep,
+ int interval, int offset)
+{
+ int ep_offset = sep->offset;
+ int ep_interval = sep->interval << 3;
+ int tmp_offset;
+ int tmp_interval;
+ int is_overlap = 0;
+
+ if (ep_interval >= interval) {
+ tmp_offset = ep_offset + ep_interval - offset;
+ tmp_interval = interval;
+ } else {
+ tmp_offset = offset + interval - ep_offset;
+ tmp_interval = ep_interval;
+ }
+ if (sep->is_in) {
+ if ((tmp_offset % tmp_interval >= 2)
+ && (tmp_offset % tmp_interval <= sep->cs_count)) {
+ is_overlap = 1;
+ }
+ } else {
+ if (tmp_offset % tmp_interval <= sep->cs_count)
+ is_overlap = 1;
+ }
+ return is_overlap;
+}
+
+
+static int count_hs_bw(struct sch_port *sport, int ep_type, int maxp,
+ int interval, int offset, int td_size)
+{
+ int i;
+ int bw_required;
+ struct sch_ep *cur_sep;
+ int ep_offset;
+ int ep_interval;
+
+ bw_required = 0;
+ for (i = 0; i < MAX_EP_NUM; i++) {
+ cur_sep = sport->hs_eps[i];
+ if (cur_sep == NULL)
+ continue;
+
+ ep_offset = cur_sep->offset;
+ ep_interval = cur_sep->interval;
+
+ if (cur_sep->is_tt &&
+ (cur_sep->ep_type == USB_ENDPOINT_XFER_ISOC)) {
+ if (is_isoc_tt_mframe_overlap(cur_sep,
+ interval, offset))
+ bw_required += 188;
+ } else {
+ if (need_more_bw_cost(ep_interval, ep_offset,
+ interval, offset))
+ bw_required += cur_sep->bw_cost;
+ }
+ }
+ bw_required += td_size;
+ return bw_required;
+}
+
+static int count_tt_isoc_bw(int is_in, int maxp, int interval,
+ int offset, int td_size, struct sch_port *sport)
+{
+ char is_cs;
+ int s_frame, s_mframe, cur_mframe;
+ int bw_required, max_bw;
+ int ss_cs_count;
+ int cs_mframe;
+ int i, j;
+ struct sch_ep *cur_sep;
+ int ep_offset;
+ int ep_interval;
+ int tt_isoc_interval;
+
+ tt_isoc_interval = interval << 3; /* frame to mframe */
+ is_cs = is_in ? 1 : 0;
+
+ s_frame = offset / 8;
+ s_mframe = offset % 8;
+ ss_cs_count = (maxp + (188 - 1)) / 188;
+ if (is_cs) {
+ cs_mframe = offset % 8 + 2 + ss_cs_count;
+ if (cs_mframe <= 6)
+ ss_cs_count += 2;
+ else if (cs_mframe == 7)
+ ss_cs_count++;
+ else if (cs_mframe > 8)
+ return -1;
+ }
+ max_bw = 0;
+
+ i = is_in ? 2 : 0;
+ for (cur_mframe = offset + i; i < ss_cs_count; cur_mframe++, i++) {
+ bw_required = 0;
+ for (j = 0; j < MAX_EP_NUM; j++) {
+ cur_sep = sport->hs_eps[j];
+ if (cur_sep == NULL)
+ continue;
+
+ ep_offset = cur_sep->offset;
+ ep_interval = cur_sep->interval;
+ if (cur_sep->is_tt &&
+ (cur_sep->ep_type == USB_ENDPOINT_XFER_ISOC)) {
+ /*
+ * isoc tt
+ * check if mframe offset overlap
+ * if overlap, add 188 to the bw
+ */
+ if (is_isoc_tt_mframe_overlap(cur_sep,
+ tt_isoc_interval, cur_mframe))
+ bw_required += 188;
+ } else if (cur_sep->ep_type == USB_ENDPOINT_XFER_INT
+ || cur_sep->ep_type == USB_ENDPOINT_XFER_ISOC) {
+ /* check if mframe */
+ if (need_more_bw_cost(ep_interval, ep_offset,
+ tt_isoc_interval, cur_mframe))
+ bw_required += cur_sep->bw_cost;
+ }
+ }
+ bw_required += 188;
+ if (bw_required > max_bw)
+ max_bw = bw_required;
+ }
+ return max_bw;
+}
+
+static int count_tt_intr_bw(int interval, int offset,
+ struct sch_port *u3h_sch_port)
+{
+ struct sch_ep *cur_sep;
+ int ep_offset;
+ int ep_interval;
+ int i;
+
+ /* check all eps in tt_intr_eps */
+ for (i = 0; i < MAX_EP_NUM; i++) {
+ cur_sep = u3h_sch_port->tt_intr_eps[i];
+ if (cur_sep == NULL)
+ continue;
+ ep_offset = cur_sep->offset;
+ ep_interval = cur_sep->interval;
+ if (need_more_bw_cost(ep_interval, ep_offset, interval, offset))
+ return SCH_FAIL;
+ }
+ return SCH_SUCCESS;
+}
+
+static int check_tt_intr_bw(struct sch_ep *sep, struct sch_port *sport)
+{
+ int frame_idx;
+ int frame_interval;
+ int interval = sep->interval;
+
+ frame_interval = interval >> 3;
+ for (frame_idx = 0; frame_idx < frame_interval; frame_idx++) {
+ if (count_tt_intr_bw(frame_interval, frame_idx, sport)
+ == SCH_SUCCESS) {
+ sep->offset = frame_idx << 3;
+ sep->pkts = 1;
+ sep->cs_count = 3;
+ sep->bw_cost = sep->maxp;
+ sep->repeat = 0;
+ return SCH_SUCCESS;
+ }
+ }
+ return SCH_FAIL;
+}
+
+static int check_tt_iso_bw(struct sch_ep *sep, struct sch_port *sport)
+{
+ int cs_count = 0;
+ int td_size;
+ int mframe_idx, frame_idx;
+ int cur_bw, best_bw, best_bw_idx;
+ int cur_offset, cs_mframe;
+ int interval;
+
+ best_bw = HS_BW_BOUND;
+ best_bw_idx = -1;
+ cur_bw = 0;
+ td_size = sep->maxp;
+ interval = sep->interval >> 3;
+ for (frame_idx = 0; frame_idx < interval; frame_idx++) {
+ for (mframe_idx = 0; mframe_idx < 8; mframe_idx++) {
+ cur_offset = (frame_idx * 8) + mframe_idx;
+ cur_bw = count_tt_isoc_bw(sep->is_in, sep->maxp,
+ interval, cur_offset, td_size, sport);
+ if (cur_bw >= 0 && cur_bw < best_bw) {
+ best_bw_idx = cur_offset;
+ best_bw = cur_bw;
+ if (cur_bw == td_size ||
+ cur_bw < (HS_BW_BOUND >> 1))
+ goto found_best_offset;
+ }
+ }
+ }
+ if (best_bw_idx == -1)
+ return SCH_FAIL;
+
+found_best_offset:
+ sep->offset = best_bw_idx;
+ sep->pkts = 1;
+ cs_count = (sep->maxp + (188 - 1)) / 188;
+ if (sep->is_in) {
+ cs_mframe = (sep->offset >> 3) + 2 + cs_count;
+ if (cs_mframe <= 6)
+ cs_count += 2;
+ else if (cs_mframe == 7)
+ cs_count++;
+ }
+ sep->cs_count = cs_count;
+ sep->bw_cost = 188;
+ sep->repeat = 0;
+
+ return SCH_SUCCESS;
+}
+
+static int check_hs_bw(struct sch_ep *sep, struct sch_port *sport)
+{
+ int td_size;
+ int cur_bw, best_bw, best_bw_idx;
+ int cur_offset;
+ int interval = sep->interval;
+
+ best_bw = HS_BW_BOUND;
+ best_bw_idx = -1;
+ cur_bw = 0;
+ td_size = sep->maxp * (sep->burst + 1);
+ for (cur_offset = 0; cur_offset < interval; cur_offset++) {
+ cur_bw = count_hs_bw(sport, sep->ep_type, sep->maxp, interval,
+ cur_offset, td_size);
+ if (cur_bw >= 0 && cur_bw < best_bw) {
+ best_bw_idx = cur_offset;
+ best_bw = cur_bw;
+ if (cur_bw == td_size || cur_bw < (HS_BW_BOUND >> 1))
+ break;
+ }
+ }
+ if (best_bw_idx == -1)
+ return SCH_FAIL;
+
+ sep->offset = best_bw_idx;
+ sep->pkts = sep->burst + 1;
+ sep->cs_count = 0;
+ sep->bw_cost = td_size;
+ sep->repeat = 0;
+
+ return SCH_SUCCESS;
+}
+
+static int check_ss_bw(struct sch_ep *sep, struct sch_port *sport)
+{
+ int cur_bw, best_bw, best_bw_idx;
+ int repeat, max_repeat, best_bw_repeat;
+ int maxp = sep->maxp;
+ int interval = sep->interval;
+ int burst = sep->burst;
+ int mult = sep->mult;
+ int frame_idx;
+ int td_size;
+
+ best_bw = SS_BW_BOUND;
+ best_bw_idx = -1;
+ cur_bw = 0;
+ td_size = maxp * (mult + 1) * (burst + 1);
+ if (mult == 0)
+ max_repeat = 0;
+ else
+ max_repeat = (interval - 1) / (mult + 1);
+
+ best_bw_repeat = 0;
+ for (frame_idx = 0; frame_idx < interval; frame_idx++) {
+ for (repeat = max_repeat; repeat >= 0; repeat--) {
+ cur_bw = count_ss_bw(sport, sep,
+ frame_idx, repeat, td_size);
+ if (cur_bw >= 0 && cur_bw < best_bw) {
+ best_bw_idx = frame_idx;
+ best_bw_repeat = repeat;
+ best_bw = cur_bw;
+ if (cur_bw <= td_size ||
+ cur_bw < (SS_BW_BOUND >> 1))
+ goto found_best_offset;
+ }
+ }
+ }
+ if (best_bw_idx == -1)
+ return SCH_FAIL;
+
+found_best_offset:
+ sep->offset = best_bw_idx;
+ sep->cs_count = 0;
+ sep->repeat = best_bw_repeat;
+ if (sep->repeat == 0) {
+ sep->bw_cost = (burst + 1) * (mult + 1) * maxp;
+ sep->pkts = (burst + 1) * (mult + 1);
+ } else {
+ sep->bw_cost = (burst + 1) * maxp;
+ sep->pkts = (burst + 1);
+ }
+
+ return SCH_SUCCESS;
+
+}
+
+static struct sch_port *xhci_to_sch_port(struct xhci_hcd *xhci, int rh_port)
+{
+ struct sch_port *port_array = (struct sch_port *)xhci->sch_ports;
+
+ if (rh_port < 1 || rh_port > MAX_PORT_NUM)
+ return NULL;
+
+ return port_array + (rh_port - 1);
+}
+
+static struct sch_ep *scheduler_remove_ep(struct xhci_hcd *xhci,
+ int rh_port, int speed, int is_tt, struct usb_host_endpoint *ep)
+{
+ struct sch_ep **ep_array;
+ struct sch_ep *cur_ep;
+ struct sch_port *sport;
+ int is_in;
+ int i;
+
+ sport = xhci_to_sch_port(xhci, rh_port);
+ if (sport == NULL) {
+ xhci_err(xhci, "can't get sch_port for rh-port%d\n", rh_port);
+ return NULL;
+ }
+ is_in = usb_endpoint_dir_in(&ep->desc);
+ if (is_in && speed == USB_SPEED_SUPER)
+ ep_array = sport->ss_in_eps;
+ else if (speed == USB_SPEED_SUPER)
+ ep_array = sport->ss_out_eps;
+ else if (speed == USB_SPEED_HIGH ||
+ (is_tt && usb_endpoint_xfer_isoc(&ep->desc)))
+ ep_array = sport->hs_eps;
+ else
+ ep_array = sport->tt_intr_eps;
+
+ for (i = 0; i < MAX_EP_NUM; i++) {
+ cur_ep = ep_array[i];
+ if (cur_ep != NULL && cur_ep->ep == ep) {
+ ep_array[i] = NULL;
+ return cur_ep;
+ }
+ }
+ return NULL;
+}
+
+static int scheduler_add_ep(struct xhci_hcd *xhci, struct sch_ep *sep)
+{
+ struct sch_port *sport;
+ struct xhci_ep_ctx *ep_ctx;
+ int speed, is_tt, ep_type;
+ int ret = SCH_SUCCESS;
+
+ speed = sep->speed;
+ is_tt = sep->is_tt;
+ ep_type = sep->ep_type;
+ ep_ctx = sep->ep_ctx;
+ sport = xhci_to_sch_port(xhci, sep->rh_port);
+ if (sport == NULL) {
+ xhci_dbg(xhci, "can't get sep for rh-port%d\n", sep->rh_port);
+ return SCH_FAIL;
+ }
+
+ xhci_dbg(xhci, "add_ep -- rh_port:%d, speed:%d, in:%d tt:%d ep_type:%d\n",
+ sep->rh_port, speed, sep->is_in, is_tt, ep_type);
+ xhci_dbg(xhci, "\t maxp:%d, interval:%d, burst:%d, mult:%d, ep:0x%p\n",
+ sep->maxp, sep->interval, sep->burst, sep->mult,
+ sep->ep);
+ /* only process special cases */
+ if (is_tt && ep_type == USB_ENDPOINT_XFER_INT &&
+ ((speed == USB_SPEED_LOW) || (speed == USB_SPEED_FULL)))
+ ret = check_tt_intr_bw(sep, sport);
+ else if (is_tt && ep_type == USB_ENDPOINT_XFER_ISOC)
+ ret = check_tt_iso_bw(sep, sport);
+ else if (speed == USB_SPEED_HIGH &&
+ (ep_type == USB_ENDPOINT_XFER_INT ||
+ ep_type == USB_ENDPOINT_XFER_ISOC))
+ ret = check_hs_bw(sep, sport);
+ else if (speed == USB_SPEED_SUPER &&
+ (ep_type == USB_ENDPOINT_XFER_INT ||
+ ep_type == USB_ENDPOINT_XFER_ISOC))
+ ret = check_ss_bw(sep, sport);
+ else
+ sep->pkts = 1;
+
+ if (ret == SCH_FAIL)
+ return ret;
+
+ /* all transfers are fixed as burst mode-1 */
+ sep->burst_mode = 1;
+
+ if (add_sch_ep(sep, sport) == SCH_FAIL) {
+ xhci_err(xhci, "%s: no space to save sch_ep\n", __func__);
+ return SCH_FAIL;
+ }
+
+ ep_ctx->reserved[0] |= (EP_BPKTS(sep->pkts) |
+ EP_BCSCOUNT(sep->cs_count) |
+ EP_BBM(sep->burst_mode));
+ ep_ctx->reserved[1] |= (EP_BOFFSET(sep->offset) |
+ EP_BREPEAT(sep->repeat));
+ xhci_dbg(xhci, "\tBPKTS:%x, BCSCOUNT:%x, BBM:%x, BOFFSET:%x, BREPEAT:%x\n",
+ sep->pkts, sep->cs_count, sep->burst_mode, sep->offset,
+ sep->repeat);
+
+ return SCH_SUCCESS;
+}
+
+
+int xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
+{
+ struct xhci_hcd *xhci;
+ struct xhci_slot_ctx *slot_ctx;
+ struct xhci_virt_device *vdev;
+ struct sch_ep *sch_ep = NULL;
+ int is_tt;
+ int rh_port;
+
+ xhci = hcd_to_xhci(hcd);
+ vdev = xhci->devs[udev->slot_id];
+ slot_ctx = xhci_get_slot_ctx(xhci, vdev->out_ctx);
+ is_tt = !!(slot_ctx->tt_info & TT_SLOT);
+ rh_port = DEVINFO_TO_ROOT_HUB_PORT(slot_ctx->dev_info2);
+
+ sch_ep = scheduler_remove_ep(xhci, rh_port, udev->speed, is_tt, ep);
+ if (sch_ep != NULL) {
+ kfree(sch_ep);
+ xhci_dbg(xhci, "remove ep:0x%p\n", ep);
+ } else
+ xhci_warn(xhci, "don't find sch_ep when drop ep(0x%p)\n", ep);
+
+ return 0;
+}
+
+
+int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
+{
+ struct xhci_hcd *xhci;
+ struct xhci_virt_device *virt_dev;
+ struct xhci_container_ctx *in_ctx;
+ struct xhci_slot_ctx *slot_ctx;
+ struct xhci_ep_ctx *in_ep_ctx;
+ struct sch_ep *sch_ep;
+ unsigned int ep_index;
+
+ xhci = hcd_to_xhci(hcd);
+ /* sch_ep struct should init as zero */
+ sch_ep = kzalloc(sizeof(struct sch_ep), GFP_KERNEL);
+ if (sch_ep == NULL)
+ return -ENOMEM;
+
+ virt_dev = xhci->devs[udev->slot_id];
+ in_ctx = virt_dev->in_ctx;
+ ep_index = xhci_get_endpoint_index(&ep->desc);
+ in_ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, ep_index);
+ slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx);
+ sch_ep->is_tt = !!(slot_ctx->tt_info & TT_SLOT);
+
+ if (usb_endpoint_xfer_int(&ep->desc))
+ sch_ep->ep_type = USB_ENDPOINT_XFER_INT;
+ else if (usb_endpoint_xfer_isoc(&ep->desc))
+ sch_ep->ep_type = USB_ENDPOINT_XFER_ISOC;
+ else if (usb_endpoint_xfer_bulk(&ep->desc))
+ sch_ep->ep_type = USB_ENDPOINT_XFER_BULK;
+
+ if (udev->speed == USB_SPEED_FULL || udev->speed == USB_SPEED_HIGH
+ || udev->speed == USB_SPEED_LOW) {
+ sch_ep->burst = (usb_endpoint_maxp(&ep->desc) & 0x1800) >> 11;
+ sch_ep->mult = 0;
+ } else if (udev->speed == USB_SPEED_SUPER) {
+ sch_ep->burst = ep->ss_ep_comp.bMaxBurst;
+ sch_ep->mult = ep->ss_ep_comp.bmAttributes & 0x3;
+ }
+ sch_ep->maxp = GET_MAX_PACKET(usb_endpoint_maxp(&ep->desc));
+ sch_ep->speed = udev->speed;
+ sch_ep->interval = EP_INTERVAL_TO_UFRAMES(in_ep_ctx->ep_info);
+ sch_ep->is_in = usb_endpoint_dir_in(&ep->desc);
+ sch_ep->ep = ep;
+ sch_ep->rh_port = DEVINFO_TO_ROOT_HUB_PORT(slot_ctx->dev_info2);
+ sch_ep->ep_ctx = in_ep_ctx;
+
+ if (scheduler_add_ep(xhci, sch_ep) != SCH_SUCCESS) {
+ kfree(sch_ep);
+ xhci_err(xhci, "there is not enough bandwidth for mtk xhci\n");
+ return -ENOSPC;
+ }
+ return 0;
+}
+
+/* @dev : struct device pointer of xhci platform_device */
+int xhci_mtk_init_quirk(struct xhci_hcd *xhci)
+{
+ size_t size = sizeof(struct sch_port) * MAX_PORT_NUM;
+
+ xhci->sch_ports = kzalloc(size, GFP_KERNEL);
+ if (xhci->sch_ports == NULL)
+ return -ENOMEM;
+
+ xhci->quirks |= XHCI_MTK_HOST;
+
+ return 0;
+}
+
+void xhci_mtk_exit_quirk(struct xhci_hcd *xhci)
+{
+ kfree(xhci->sch_ports);
+}
+
+/*
+ * The TD size is the number of bytes remaining in the TD (including this TRB),
+ * right shifted by 10.
+ * It must fit in bits 21:17, so it can't be bigger than 31.
+ */
+u32 xhci_mtk_td_remainder_quirk(unsigned int td_running_total,
+ unsigned trb_buffer_length, struct urb *urb)
+{
+ u32 max = 31;
+ int remainder, td_packet_count, packet_transferred;
+ unsigned int td_transfer_size = urb->transfer_buffer_length;
+ unsigned int maxp;
+
+
+ /* no scatter-gather for control transfer */
+ if (usb_endpoint_xfer_control(&urb->ep->desc))
+ return 0;
+
+ maxp = GET_MAX_PACKET(usb_endpoint_maxp(&urb->ep->desc));
+
+ /* 0 for the last TRB */
+ if (td_running_total + trb_buffer_length == td_transfer_size)
+ return 0;
+
+ packet_transferred = td_running_total / maxp;
+ td_packet_count = DIV_ROUND_UP(td_transfer_size, maxp);
+ remainder = td_packet_count - packet_transferred;
+
+ if (remainder > max)
+ return max << 17;
+ else
+ return remainder << 17;
+}
+
+
diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h
new file mode 100644
index 000000000000..f27890344e1b
--- /dev/null
+++ b/drivers/usb/host/xhci-mtk.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ * Author: Chunfeng.Yun <chunfeng.yun@mediatek.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _XHCI_MTK_H_
+#define _XHCI_MTK_H_
+
+
+#define MAX_EP_NUM 32
+
+struct sch_ep {
+ int rh_port; /* root hub port number */
+ /* device info */
+ int speed;
+ int is_tt;
+ /* ep info */
+ int is_in;
+ int ep_type;
+ int maxp;
+ int interval;
+ int burst;
+ int mult;
+ /* scheduling info */
+ int offset;
+ int repeat;
+ int pkts;
+ int cs_count;
+ int burst_mode;
+ /* other */
+ int bw_cost; /* bandwidth cost in each repeat; including overhead */
+ struct usb_host_endpoint *ep;
+ struct xhci_ep_ctx *ep_ctx;
+};
+
+struct sch_port {
+ struct sch_ep *ss_out_eps[MAX_EP_NUM];
+ struct sch_ep *ss_in_eps[MAX_EP_NUM];
+ struct sch_ep *hs_eps[MAX_EP_NUM]; /* including tt isoc */
+ struct sch_ep *tt_intr_eps[MAX_EP_NUM];
+};
+
+
+#if IS_ENABLED(CONFIG_USB_XHCI_MTK)
+
+int xhci_mtk_init_quirk(struct xhci_hcd *xhci);
+void xhci_mtk_exit_quirk(struct xhci_hcd *xhci);
+int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
+int xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep);
+u32 xhci_mtk_td_remainder_quirk(unsigned int td_running_total,
+ unsigned trb_buffer_length, struct urb *urb);
+
+#else
+static inline int xhci_mtk_init_quirk(struct xhci_hcd *xhci)
+{
+ return 0;
+}
+
+static inline void xhci_mtk_exit_quirk(struct xhci_hcd *xhci)
+{
+}
+
+static inline int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd,
+ struct usb_device *udev, struct usb_host_endpoint *ep)
+{
+ return 0;
+}
+
+static inline int xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd,
+ struct usb_device *udev, struct usb_host_endpoint *ep)
+{
+ return 0;
+}
+
+u32 xhci_mtk_td_remainder_quirk(unsigned int td_running_total,
+ unsigned trb_buffer_length, struct urb *urb)
+{
+ return 0;
+}
+
+#endif
+
+#endif /* _XHCI_MTK_H_ */