mb();
}
+/**
+ * fiq_fsm_restart_np_pending() - Restart a single non-periodic contended transfer
+ * @st: Pointer to the channel's state
+ * @num_channels: Total number of host channels
+ * @orig_channel: Channel index of completed transfer
+ *
+ * In the case where an IN and OUT transfer are simultaneously scheduled to the
+ * same device/EP, inadequate hub implementations will misbehave. Once the first
+ * transfer is complete, a pending non-periodic split can then be issued.
+ */
+static void notrace fiq_fsm_restart_np_pending(struct fiq_state *st, int num_channels, int orig_channel)
+{
+ int i;
+ int dev_addr = st->channel[orig_channel].hcchar_copy.b.devaddr;
+ int ep_num = st->channel[orig_channel].hcchar_copy.b.epnum;
+ for (i = 0; i < num_channels; i++) {
+ if (st->channel[i].fsm == FIQ_NP_SSPLIT_PENDING &&
+ st->channel[i].hcchar_copy.b.devaddr == dev_addr &&
+ st->channel[i].hcchar_copy.b.epnum == ep_num) {
+ st->channel[i].fsm = FIQ_NP_SSPLIT_STARTED;
+ fiq_fsm_restart_channel(st, i, 0);
+ break;
+ }
+ }
+}
+
static inline int notrace fiq_get_xfer_len(struct fiq_state *st, int n)
{
/* The xfersize register is a bit wonky. For IN transfers, it decrements by the packet size. */
st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
}
}
+ if (st->fsm != FIQ_NP_IN_CSPLIT_RETRY) {
+ fiq_fsm_restart_np_pending(state, num_channels, n);
+ }
break;
case FIQ_NP_OUT_CSPLIT_RETRY:
st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
}
}
+ if (st->fsm != FIQ_NP_OUT_CSPLIT_RETRY) {
+ fiq_fsm_restart_np_pending(state, num_channels, n);
+ }
break;
/* Periodic split states (except isoc out) */
/* Nonperiodic state groups */
FIQ_NP_SSPLIT_STARTED = 1,
FIQ_NP_SSPLIT_RETRY = 2,
+ /* TT contention - working around hub bugs */
+ FIQ_NP_SSPLIT_PENDING = 33,
FIQ_NP_OUT_CSPLIT_RETRY = 3,
FIQ_NP_IN_CSPLIT_RETRY = 4,
FIQ_NP_SPLIT_DONE = 5,
}
}
+/**
+ * fiq_fsm_np_tt_contended() - Avoid performing contended non-periodic transfers
+ * @hcd: Pointer to the dwc_otg_hcd struct
+ * @qh: Pointer to the endpoint's queue head
+ *
+ * Certain hub chips don't differentiate between IN and OUT non-periodic pipes
+ * with the same endpoint number. If transfers get completed out of order
+ * (disregarding the direction token) then the hub can lock up
+ * or return erroneous responses.
+ *
+ * Returns 1 if initiating the transfer would cause contention, 0 otherwise.
+ */
+int fiq_fsm_np_tt_contended(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
+ int i;
+ struct fiq_channel_state *st;
+ int dev_addr = qh->channel->dev_addr;
+ int ep_num = qh->channel->ep_num;
+ for (i = 0; i < hcd->core_if->core_params->host_channels; i++) {
+ if (i == qh->channel->hc_num)
+ continue;
+ st = &hcd->fiq_state->channel[i];
+ switch (st->fsm) {
+ case FIQ_NP_SSPLIT_STARTED:
+ case FIQ_NP_SSPLIT_RETRY:
+ case FIQ_NP_SSPLIT_PENDING:
+ case FIQ_NP_OUT_CSPLIT_RETRY:
+ case FIQ_NP_IN_CSPLIT_RETRY:
+ if (st->hcchar_copy.b.devaddr == dev_addr &&
+ st->hcchar_copy.b.epnum == ep_num)
+ return 1;
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
/*
* Pushing a periodic request into the queue near the EOF1 point
* in a microframe causes erroneous behaviour (frmovrun) interrupt.
switch (hc->ep_type) {
case UE_CONTROL:
case UE_BULK:
- st->fsm = FIQ_NP_SSPLIT_STARTED;
+ if (fiq_fsm_np_tt_contended(hcd, qh)) {
+ st->fsm = FIQ_NP_SSPLIT_PENDING;
+ start_immediate = 0;
+ } else {
+ st->fsm = FIQ_NP_SSPLIT_STARTED;
+ }
break;
case UE_ISOCHRONOUS:
if (hc->ep_is_in) {
break;
case UE_INTERRUPT:
if (fiq_fsm_mask & 0x8) {
- st->fsm = FIQ_NP_SSPLIT_STARTED;
+ if (fiq_fsm_np_tt_contended(hcd, qh)) {
+ st->fsm = FIQ_NP_SSPLIT_PENDING;
+ start_immediate = 0;
+ } else {
+ st->fsm = FIQ_NP_SSPLIT_STARTED;
+ }
} else if (start_immediate) {
st->fsm = FIQ_PER_SSPLIT_STARTED;
} else {