Commit d92021f6 authored by Thinh Nguyen's avatar Thinh Nguyen Committed by Felipe Balbi

usb: dwc3: Add workaround for isoc start transfer failure

In DWC_usb31 version 1.70a-ea06 and prior, for highspeed and fullspeed
isochronous IN, BIT[15:14] of the 16-bit microframe number reported by
the XferNotReady event are invalid. The driver uses this number to
schedule the isochronous transfer and passes it to the START TRANSFER
command. Because this number is invalid, the command may fail. If
BIT[15:14] matches the internal 16-bit microframe, the START TRANSFER
command will pass and the transfer will start at the scheduled time, if
it is off by 1, the command will still pass, but the transfer will start
2 seconds in the future. For all other conditions, the START TRANSFER
command will fail with bus-expiry.

In order to workaround this issue, we can test for the correct
combination of BIT[15:14] by sending START TRANSFER commands with
different values of BIT[15:14]: 'b00, 'b01, 'b10, and 'b11. Each
combination is 2^14 uframe apart (or 2 seconds). 4 seconds into the
future will result in a bus-expiry status. As the result, within the 4
possible combinations for BIT[15:14], there will be 2 successful and 2
failure START COMMAND status. One of the 2 successful command status
will result in a 2-second delay start. The smaller BIT[15:14] value is
the correct combination.

Since there are only 4 outcomes and the results are ordered, we can
simply test 2 START TRANSFER commands with BIT[15:14] combinations 'b00
and 'b01 to deduce the smaller successful combination.

Let test0 = test status for combination 'b00 and test1 = test status for
'b01 of BIT[15:14]. The correct combination is as follow:

if test0 fails and test1 passes, BIT[15:14] is 'b01
if test0 fails and test1 fails, BIT[15:14] is 'b10
if test0 passes and test1 fails, BIT[15:14] is 'b11
if test0 passes and test1 passes, BIT[15:14] is 'b00

Synopsys STAR 9001202023: Wrong microframe number for isochronous IN
endpoints.
Signed-off-by: default avatarThinh Nguyen <thinhn@synopsys.com>
Signed-off-by: default avatarFelipe Balbi <felipe.balbi@linux.intel.com>
parent dd74b96c
...@@ -1248,6 +1248,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) ...@@ -1248,6 +1248,8 @@ static void dwc3_get_properties(struct dwc3 *dwc)
"snps,is-utmi-l1-suspend"); "snps,is-utmi-l1-suspend");
device_property_read_u8(dev, "snps,hird-threshold", device_property_read_u8(dev, "snps,hird-threshold",
&hird_threshold); &hird_threshold);
dwc->dis_start_transfer_quirk = device_property_read_bool(dev,
"snps,dis-start-transfer-quirk");
dwc->usb3_lpm_capable = device_property_read_bool(dev, dwc->usb3_lpm_capable = device_property_read_bool(dev,
"snps,usb3_lpm_capable"); "snps,usb3_lpm_capable");
dwc->usb2_lpm_disable = device_property_read_bool(dev, dwc->usb2_lpm_disable = device_property_read_bool(dev,
......
...@@ -666,6 +666,10 @@ struct dwc3_event_buffer { ...@@ -666,6 +666,10 @@ struct dwc3_event_buffer {
* @name: a human readable name e.g. ep1out-bulk * @name: a human readable name e.g. ep1out-bulk
* @direction: true for TX, false for RX * @direction: true for TX, false for RX
* @stream_capable: true when streams are enabled * @stream_capable: true when streams are enabled
* @combo_num: the test combination BIT[15:14] of the frame number to test
* isochronous START TRANSFER command failure workaround
* @start_cmd_status: the status of testing START TRANSFER command with
* combo_num = 'b00
*/ */
struct dwc3_ep { struct dwc3_ep {
struct usb_ep endpoint; struct usb_ep endpoint;
...@@ -715,6 +719,10 @@ struct dwc3_ep { ...@@ -715,6 +719,10 @@ struct dwc3_ep {
unsigned direction:1; unsigned direction:1;
unsigned stream_capable:1; unsigned stream_capable:1;
/* For isochronous START TRANSFER workaround only */
u8 combo_num;
int start_cmd_status;
}; };
enum dwc3_phy { enum dwc3_phy {
...@@ -982,6 +990,8 @@ struct dwc3_scratchpad_array { ...@@ -982,6 +990,8 @@ struct dwc3_scratchpad_array {
* @pullups_connected: true when Run/Stop bit is set * @pullups_connected: true when Run/Stop bit is set
* @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround
* @three_stage_setup: set if we perform a three phase setup * @three_stage_setup: set if we perform a three phase setup
* @dis_start_transfer_quirk: set if start_transfer failure SW workaround is
* not needed for DWC_usb31 version 1.70a-ea06 and below
* @usb3_lpm_capable: set if hadrware supports Link Power Management * @usb3_lpm_capable: set if hadrware supports Link Power Management
* @usb2_lpm_disable: set to disable usb2 lpm * @usb2_lpm_disable: set to disable usb2 lpm
* @disable_scramble_quirk: set if we enable the disable scramble quirk * @disable_scramble_quirk: set if we enable the disable scramble quirk
...@@ -1117,6 +1127,8 @@ struct dwc3 { ...@@ -1117,6 +1127,8 @@ struct dwc3 {
#define DWC3_REVISION_IS_DWC31 0x80000000 #define DWC3_REVISION_IS_DWC31 0x80000000
#define DWC3_USB31_REVISION_110A (0x3131302a | DWC3_REVISION_IS_DWC31) #define DWC3_USB31_REVISION_110A (0x3131302a | DWC3_REVISION_IS_DWC31)
#define DWC3_USB31_REVISION_120A (0x3132302a | DWC3_REVISION_IS_DWC31) #define DWC3_USB31_REVISION_120A (0x3132302a | DWC3_REVISION_IS_DWC31)
#define DWC3_USB31_REVISION_160A (0x3136302a | DWC3_REVISION_IS_DWC31)
#define DWC3_USB31_REVISION_170A (0x3137302a | DWC3_REVISION_IS_DWC31)
u32 version_type; u32 version_type;
...@@ -1170,6 +1182,7 @@ struct dwc3 { ...@@ -1170,6 +1182,7 @@ struct dwc3 {
unsigned pullups_connected:1; unsigned pullups_connected:1;
unsigned setup_packet_pending:1; unsigned setup_packet_pending:1;
unsigned three_stage_setup:1; unsigned three_stage_setup:1;
unsigned dis_start_transfer_quirk:1;
unsigned usb3_lpm_capable:1; unsigned usb3_lpm_capable:1;
unsigned usb2_lpm_disable:1; unsigned usb2_lpm_disable:1;
......
...@@ -1263,8 +1263,125 @@ static int __dwc3_gadget_get_frame(struct dwc3 *dwc) ...@@ -1263,8 +1263,125 @@ static int __dwc3_gadget_get_frame(struct dwc3 *dwc)
return DWC3_DSTS_SOFFN(reg); return DWC3_DSTS_SOFFN(reg);
} }
/**
* dwc3_gadget_start_isoc_quirk - workaround invalid frame number
* @dep: isoc endpoint
*
* This function tests for the correct combination of BIT[15:14] from the 16-bit
* microframe number reported by the XferNotReady event for the future frame
* number to start the isoc transfer.
*
* In DWC_usb31 version 1.70a-ea06 and prior, for highspeed and fullspeed
* isochronous IN, BIT[15:14] of the 16-bit microframe number reported by the
* XferNotReady event are invalid. The driver uses this number to schedule the
* isochronous transfer and passes it to the START TRANSFER command. Because
* this number is invalid, the command may fail. If BIT[15:14] matches the
* internal 16-bit microframe, the START TRANSFER command will pass and the
* transfer will start at the scheduled time, if it is off by 1, the command
* will still pass, but the transfer will start 2 seconds in the future. For all
* other conditions, the START TRANSFER command will fail with bus-expiry.
*
* In order to workaround this issue, we can test for the correct combination of
* BIT[15:14] by sending START TRANSFER commands with different values of
* BIT[15:14]: 'b00, 'b01, 'b10, and 'b11. Each combination is 2^14 uframe apart
* (or 2 seconds). 4 seconds into the future will result in a bus-expiry status.
* As the result, within the 4 possible combinations for BIT[15:14], there will
* be 2 successful and 2 failure START COMMAND status. One of the 2 successful
* command status will result in a 2-second delay start. The smaller BIT[15:14]
* value is the correct combination.
*
* Since there are only 4 outcomes and the results are ordered, we can simply
* test 2 START TRANSFER commands with BIT[15:14] combinations 'b00 and 'b01 to
* deduce the smaller successful combination.
*
* Let test0 = test status for combination 'b00 and test1 = test status for 'b01
* of BIT[15:14]. The correct combination is as follow:
*
* if test0 fails and test1 passes, BIT[15:14] is 'b01
* if test0 fails and test1 fails, BIT[15:14] is 'b10
* if test0 passes and test1 fails, BIT[15:14] is 'b11
* if test0 passes and test1 passes, BIT[15:14] is 'b00
*
* Synopsys STAR 9001202023: Wrong microframe number for isochronous IN
* endpoints.
*/
static void dwc3_gadget_start_isoc_quirk(struct dwc3_ep *dep)
{
int cmd_status = 0;
bool test0;
bool test1;
while (dep->combo_num < 2) {
struct dwc3_gadget_ep_cmd_params params;
u32 test_frame_number;
u32 cmd;
/*
* Check if we can start isoc transfer on the next interval or
* 4 uframes in the future with BIT[15:14] as dep->combo_num
*/
test_frame_number = dep->frame_number & 0x3fff;
test_frame_number |= dep->combo_num << 14;
test_frame_number += max_t(u32, 4, dep->interval);
params.param0 = upper_32_bits(dep->dwc->bounce_addr);
params.param1 = lower_32_bits(dep->dwc->bounce_addr);
cmd = DWC3_DEPCMD_STARTTRANSFER;
cmd |= DWC3_DEPCMD_PARAM(test_frame_number);
cmd_status = dwc3_send_gadget_ep_cmd(dep, cmd, &params);
/* Redo if some other failure beside bus-expiry is received */
if (cmd_status && cmd_status != -EAGAIN) {
dep->start_cmd_status = 0;
dep->combo_num = 0;
return;
}
/* Store the first test status */
if (dep->combo_num == 0)
dep->start_cmd_status = cmd_status;
dep->combo_num++;
/*
* End the transfer if the START_TRANSFER command is successful
* to wait for the next XferNotReady to test the command again
*/
if (cmd_status == 0) {
dwc3_stop_active_transfer(dep, true);
return;
}
}
/* test0 and test1 are both completed at this point */
test0 = (dep->start_cmd_status == 0);
test1 = (cmd_status == 0);
if (!test0 && test1)
dep->combo_num = 1;
else if (!test0 && !test1)
dep->combo_num = 2;
else if (test0 && !test1)
dep->combo_num = 3;
else if (test0 && test1)
dep->combo_num = 0;
dep->frame_number &= 0x3fff;
dep->frame_number |= dep->combo_num << 14;
dep->frame_number += max_t(u32, 4, dep->interval);
/* Reinitialize test variables */
dep->start_cmd_status = 0;
dep->combo_num = 0;
__dwc3_gadget_kick_transfer(dep);
}
static void __dwc3_gadget_start_isoc(struct dwc3_ep *dep) static void __dwc3_gadget_start_isoc(struct dwc3_ep *dep)
{ {
struct dwc3 *dwc = dep->dwc;
if (list_empty(&dep->pending_list)) { if (list_empty(&dep->pending_list)) {
dev_info(dep->dwc->dev, "%s: ran out of requests\n", dev_info(dep->dwc->dev, "%s: ran out of requests\n",
dep->name); dep->name);
...@@ -1272,6 +1389,18 @@ static void __dwc3_gadget_start_isoc(struct dwc3_ep *dep) ...@@ -1272,6 +1389,18 @@ static void __dwc3_gadget_start_isoc(struct dwc3_ep *dep)
return; return;
} }
if (!dwc->dis_start_transfer_quirk && dwc3_is_usb31(dwc) &&
(dwc->revision <= DWC3_USB31_REVISION_160A ||
(dwc->revision == DWC3_USB31_REVISION_170A &&
dwc->version_type >= DWC31_VERSIONTYPE_EA01 &&
dwc->version_type <= DWC31_VERSIONTYPE_EA06))) {
if (dwc->gadget.speed <= USB_SPEED_HIGH && dep->direction) {
dwc3_gadget_start_isoc_quirk(dep);
return;
}
}
dep->frame_number = DWC3_ALIGN_FRAME(dep); dep->frame_number = DWC3_ALIGN_FRAME(dep);
__dwc3_gadget_kick_transfer(dep); __dwc3_gadget_kick_transfer(dep);
} }
...@@ -2153,6 +2282,8 @@ static int dwc3_gadget_init_endpoint(struct dwc3 *dwc, u8 epnum) ...@@ -2153,6 +2282,8 @@ static int dwc3_gadget_init_endpoint(struct dwc3 *dwc, u8 epnum)
dep->direction = direction; dep->direction = direction;
dep->regs = dwc->regs + DWC3_DEP_BASE(epnum); dep->regs = dwc->regs + DWC3_DEP_BASE(epnum);
dwc->eps[epnum] = dep; dwc->eps[epnum] = dep;
dep->combo_num = 0;
dep->start_cmd_status = 0;
snprintf(dep->name, sizeof(dep->name), "ep%u%s", num, snprintf(dep->name, sizeof(dep->name), "ep%u%s", num,
direction ? "in" : "out"); direction ? "in" : "out");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment