diff options
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/dwc3/core.c | 2 | ||||
-rw-r--r-- | drivers/usb/dwc3/core.h | 13 | ||||
-rw-r--r-- | drivers/usb/dwc3/gadget.c | 131 |
3 files changed, 146 insertions, 0 deletions
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index eb064a65363c..a1b126f90261 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1248,6 +1248,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) "snps,is-utmi-l1-suspend"); device_property_read_u8(dev, "snps,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, "snps,usb3_lpm_capable"); dwc->usb2_lpm_disable = device_property_read_bool(dev, diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 709e8dd356ee..0136aa7766e1 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -666,6 +666,10 @@ struct dwc3_event_buffer { * @name: a human readable name e.g. ep1out-bulk * @direction: true for TX, false for RX * @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 usb_ep endpoint; @@ -715,6 +719,10 @@ struct dwc3_ep { unsigned direction:1; unsigned stream_capable:1; + + /* For isochronous START TRANSFER workaround only */ + u8 combo_num; + int start_cmd_status; }; enum dwc3_phy { @@ -982,6 +990,8 @@ struct dwc3_scratchpad_array { * @pullups_connected: true when Run/Stop bit is set * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround * @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 * @usb2_lpm_disable: set to disable usb2 lpm * @disable_scramble_quirk: set if we enable the disable scramble quirk @@ -1117,6 +1127,8 @@ struct dwc3 { #define DWC3_REVISION_IS_DWC31 0x80000000 #define DWC3_USB31_REVISION_110A (0x3131302a | 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; @@ -1170,6 +1182,7 @@ struct dwc3 { unsigned pullups_connected:1; unsigned setup_packet_pending:1; unsigned three_stage_setup:1; + unsigned dis_start_transfer_quirk:1; unsigned usb3_lpm_capable:1; unsigned usb2_lpm_disable:1; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 9faad896b3a1..c48ea78341a4 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1263,8 +1263,125 @@ static int __dwc3_gadget_get_frame(struct dwc3 *dwc) 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, ¶ms); + + /* 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) { + struct dwc3 *dwc = dep->dwc; + if (list_empty(&dep->pending_list)) { dev_info(dep->dwc->dev, "%s: ran out of requests\n", dep->name); @@ -1272,6 +1389,18 @@ static void __dwc3_gadget_start_isoc(struct dwc3_ep *dep) 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); __dwc3_gadget_kick_transfer(dep); } @@ -2153,6 +2282,8 @@ static int dwc3_gadget_init_endpoint(struct dwc3 *dwc, u8 epnum) dep->direction = direction; dep->regs = dwc->regs + DWC3_DEP_BASE(epnum); dwc->eps[epnum] = dep; + dep->combo_num = 0; + dep->start_cmd_status = 0; snprintf(dep->name, sizeof(dep->name), "ep%u%s", num, direction ? "in" : "out"); |