summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c76
1 files changed, 54 insertions, 22 deletions
diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
index aa0307d25..3e9091589 100644
--- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
+++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
@@ -158,6 +158,10 @@ typedef struct
// - 55 usb0 (FS) has 5x2 endpoints, usb1 (HS) has 6x2 endpoints
#define MAX_EP_PAIRS 6
+// Bounded spin waiting for hardware to clear an EPSKIP bit when retiring a still-armed endpoint on
+// reopen (dcd_edpt_open). Hardware clears it within a (micro)frame; the guard only avoids a hang.
+#define IP3511_EPSKIP_SPIN 100000u
+
// NOTE data will be transferred as soon as dcd get request by dcd_pipe(_queue)_xfer using double buffering.
// current_td is used to keep track of number of remaining & xferred bytes of the current request.
typedef struct
@@ -337,12 +341,29 @@ void dcd_sof_enable(uint8_t rhport, bool en)
//--------------------------------------------------------------------+
// DCD Endpoint Port
//--------------------------------------------------------------------+
+// Retire a still-armed (Active) endpoint the sanctioned way before its command/status entry is
+// rewritten (halt, reopen, altsetting switch). UM11126 §41.7.6/§41.8.3: write EPSKIP and wait for
+// hardware to clear the bit, then Active is safe to clear — a bare Active=0 can race a mid-packet
+// buffer. Bounded: hardware clears EPSKIP within a (micro)frame; the guard only prevents a hang.
+static void edpt_skip_active(uint8_t rhport, uint8_t ep_id) {
+ ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ if ( ep_cs[0].cmd_sts.active || ep_cs[1].cmd_sts.active ) {
+ dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
+ dcd_reg->EPSKIP |= TU_BIT(ep_id);
+ uint32_t guard = IP3511_EPSKIP_SPIN;
+ while ( (dcd_reg->EPSKIP & TU_BIT(ep_id)) && guard-- ) {}
+ }
+ ep_cs[0].cmd_sts.active = ep_cs[1].cmd_sts.active = 0;
+}
+
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr)
{
- (void) rhport;
-
// TODO cannot able to STALL Control OUT endpoint !!!!! FIXME try some walk-around
uint8_t const ep_id = ep_addr2id(ep_addr);
+ // Retire any armed buffer before setting Stall: the hardware services an armed (Active) buffer
+ // instead of returning STALL, so a halt requested while a transfer is queued would not actually
+ // stall the endpoint (usbtest case 13), and Active+Stall must not both be set.
+ edpt_skip_active(rhport, ep_id);
_dcd.ep[ep_id][0].cmd_sts.stall = 1;
}
@@ -362,9 +383,15 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
//------------- Prepare Queue Head -------------//
uint8_t ep_id = ep_addr2id(p_endpoint_desc->bEndpointAddress);
ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
- // Check if endpoint is available
- TU_ASSERT( ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable );
+ // usbd_edpt_close() is a no-op on ISO_ALLOC ports, so an endpoint a class closed then reopened
+ // across SET_INTERFACE (e.g. the video notification or audio streaming endpoint) is still armed
+ // here rather than disabled. Retire it (edpt_skip_active) before reconfiguring.
+ if ( !(ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable) ) {
+ edpt_skip_active(rhport, ep_id);
+ ep_cs[0].cmd_sts.disable = ep_cs[1].cmd_sts.disable = 1;
+ }
edpt_reset(rhport, ep_id);
@@ -389,7 +416,6 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
}
// Enable EP interrupt
- dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
dcd_reg->INTEN |= TU_BIT(ep_id);
return true;
@@ -404,29 +430,35 @@ void dcd_edpt_close_all (uint8_t rhport)
}
}
-void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
-{
- (void) rhport;
-
+bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
+ (void) largest_packet_size;
+ // Reserve the endpoint command/status entry once (persists across altsetting changes); the
+ // buffer pointer is filled per-transfer, so nothing to pre-allocate. Mirrors the ISO branch of
+ // dcd_edpt_open().
uint8_t ep_id = ep_addr2id(ep_addr);
- _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][0].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual)
- _dcd.ep[ep_id][0].cmd_sts.disable = _dcd.ep[ep_id][1].cmd_sts.disable = 1;
-}
+ ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ TU_ASSERT( ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable );
-#if 0
-bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- (void)rhport;
- (void)ep_addr;
- (void)largest_packet_size;
- return false;
+ edpt_reset(rhport, ep_id);
+ ep_cs[0].cmd_sts.type = 1; // ISO
+
+ dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
+ dcd_reg->INTEN |= TU_BIT(ep_id);
+ return true;
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
- (void)rhport;
- (void)desc_ep;
- return false;
+ // (Re)activate on altsetting selection: retire a buffer still armed from the previous altsetting
+ // (the hardware keeps servicing an Active buffer across SET_INTERFACE, fighting the class's fresh
+ // transfer), clear stall and reset the data toggle. The class re-arms via dcd_edpt_xfer().
+ uint8_t ep_id = ep_addr2id(desc_ep->bEndpointAddress);
+ ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ edpt_skip_active(rhport, ep_id);
+ ep_cs[0].cmd_sts.stall = 0;
+ ep_cs[0].cmd_sts.toggle_reset = 1;
+ ep_cs[0].cmd_sts.rf_tv = 0;
+ return true;
}
-#endif
static void prepare_ep_xfer(uint8_t rhport, uint8_t ep_id, uint16_t buf_offset, uint16_t total_bytes) {
uint16_t nbytes;