Add max nak config

Signed-off-by: Zixun LI <admin@hifiphile.com>
This commit is contained in:
Zixun LI
2025-11-27 17:17:03 +01:00
committed by HiFiPhile
parent 8914f402e5
commit 07ff25eb1e
2 changed files with 140 additions and 72 deletions

View File

@ -95,18 +95,23 @@ enum {
TUH_CFGID_INVALID = 0,
TUH_CFGID_RPI_PIO_USB_CONFIGURATION = 100, // cfg_param: pio_usb_configuration_t
TUH_CFGID_MAX3421 = 200,
TUH_CFGID_FSDEV = 300,
};
typedef struct {
uint8_t max_nak; // max NAK per endpoint per frame to save CPU/SPI bus usage
uint8_t max_nak; // max NAK per endpoint per frame to save CPU/SPI bus usage (0=unlimited)
uint8_t cpuctl; // R16: CPU Control Register
uint8_t pinctl; // R17: Pin Control Register. FDUPSPI bit is ignored
} tuh_configure_max3421_t;
typedef struct {
uint8_t max_nak; // max NAK per endpoint per frame to save CPU usage (0=unlimited)
} tuh_configure_fsdev_t;
typedef union {
// For TUH_CFGID_RPI_PIO_USB_CONFIGURATION use pio_usb_configuration_t
tuh_configure_max3421_t max3421;
tuh_configure_fsdev_t fsdev;
} tuh_configure_param_t;
//--------------------------------------------------------------------+

View File

@ -60,48 +60,56 @@
TU_VERIFY_STATIC(CFG_TUH_FSDEV_ENDPOINT_MAX <= 255, "currently only use 8-bit for index");
enum {
HCD_XFER_ERROR_MAX = 3
HCD_XFER_ERROR_MAX = 3,
HCD_XFER_NAK_MAX = 15,
HCD_XFER_NAK_DEFAULT = 3,
};
// Host driver struct for each opened endpoint
typedef struct {
uint8_t *buffer;
uint16_t buflen;
uint16_t queued_len;
uint16_t max_packet_size;
uint8_t dev_addr;
uint8_t ep_addr;
uint8_t ep_type;
uint8_t interval;
struct TU_ATTR_PACKED {
uint8_t low_speed : 1;
uint8_t ls_pre : 1;
uint8_t allocated : 1;
uint8_t next_setup : 1;
uint8_t pid : 1;
};
} hcd_endpoint_t;
// Channel direction state
typedef struct {
hcd_endpoint_t* edpt;
struct TU_ATTR_PACKED {
uint8_t allocated : 1;
uint8_t retry : 3;
uint8_t nak : 4; // Max NAK count in current frame
};
} hcd_channel_dir_t;
// Additional info for each channel when it is active
typedef struct {
hcd_endpoint_t* edpt[2]; // OUT/IN
uint16_t queued_len[2];
uint8_t dev_addr;
uint8_t ep_num;
uint8_t ep_type;
uint8_t allocated[2];
uint8_t retry[2];
hcd_channel_dir_t out, in;
} hcd_channel_t;
// Root hub port state
static struct {
bool connected;
} _hcd_port;
typedef struct {
hcd_channel_t channel[FSDEV_EP_COUNT];
hcd_endpoint_t edpt[CFG_TUH_FSDEV_ENDPOINT_MAX];
} hcd_data_t;
bool connected;
} _hcd_data;
hcd_data_t _hcd_data;
static tuh_configure_fsdev_t _tuh_cfg = {
.max_nak = HCD_XFER_NAK_DEFAULT,
};
//--------------------------------------------------------------------+
// Prototypes
@ -114,7 +122,6 @@ static uint8_t channel_alloc(uint8_t dev_addr, uint8_t ep_addr, uint8_t ep_type)
static bool edpt_xfer_kickoff(uint8_t ep_id);
static bool channel_xfer_start(uint8_t ch_id, tusb_dir_t dir);
static void edpoint_close(uint8_t ep_id);
static void port_status_handler(uint8_t rhport, bool in_isr);
static void ch_handle_ack(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir);
static void ch_handle_nak(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir);
static void ch_handle_stall(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir);
@ -129,7 +136,11 @@ static inline void endpoint_dealloc(hcd_endpoint_t* edpt) {
}
static inline void channel_dealloc(hcd_channel_t* ch, tusb_dir_t dir) {
ch->allocated[dir] = 0;
if (dir == TUSB_DIR_OUT) {
ch->out.allocated = 0;
} else {
ch->in.allocated = 0;
}
}
// Write channel state in specified direction
@ -194,9 +205,11 @@ static inline uint16_t channel_get_rx_count(uint8_t ch_id) {
// Optional HCD configuration, called by tuh_configure()
bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) {
(void) rhport;
(void) cfg_id;
(void) cfg_param;
return false;
TU_VERIFY(cfg_id == TUH_CFGID_FSDEV && cfg_param != NULL);
tuh_configure_param_t const* cfg = (tuh_configure_param_t const*) cfg_param;
_tuh_cfg.max_nak = tu_min8(cfg->fsdev.max_nak, HCD_XFER_NAK_MAX);
return true;
}
// Initialize controller to host mode
@ -210,11 +223,11 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
tu_memclr(&_hcd_data, sizeof(_hcd_data));
// Enable interrupts for host mode
FSDEV_REG->CNTR |= USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SUSPM |
FSDEV_REG->CNTR |= USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SOFM | USB_CNTR_SUSPM |
USB_CNTR_WKUPM | USB_CNTR_ERRM | USB_CNTR_PMAOVRM;
// Initialize port state
_hcd_port.connected = false;
_hcd_data.connected = false;
fsdev_connect(rhport);
@ -229,35 +242,48 @@ bool hcd_deinit(uint8_t rhport) {
return true;
}
static void port_status_handler(uint8_t rhport, bool in_isr) {
//--------------------------------------------------------------------+
// Interrupt Helper Functions
//--------------------------------------------------------------------+
static inline void sof_handler(void) {
// Reset NAK counters for all active channels
for (uint8_t ch_id = 0; ch_id < FSDEV_EP_COUNT; ch_id++) {
hcd_channel_t* channel = &_hcd_data.channel[ch_id];
if (channel->out.allocated) {
channel->out.nak = 0;
}
if (channel->in.allocated) {
channel->in.nak = 0;
}
}
}
static inline void port_status_handler(uint8_t rhport, bool in_isr) {
uint32_t const fnr_reg = FSDEV_REG->FNR;
uint32_t const istr_reg = FSDEV_REG->ISTR;
// SE0 detected USB Disconnected state
if ((fnr_reg & (USB_FNR_RXDP | USB_FNR_RXDM)) == 0U) {
_hcd_port.connected = false;
_hcd_data.connected = false;
hcd_event_device_remove(rhport, in_isr);
return;
}
if (!_hcd_port.connected) {
if (!_hcd_data.connected) {
// J-state or K-state detected & LastState=Disconnected
if (((fnr_reg & USB_FNR_RXDP) != 0U) || ((istr_reg & USB_ISTR_LS_DCONN) != 0U)) {
_hcd_port.connected = true;
_hcd_data.connected = true;
hcd_event_device_attach(rhport, in_isr);
}
} else {
// J-state or K-state detected & lastState=Connected: a Missed disconnection is detected
if (((fnr_reg & USB_FNR_RXDP) != 0U) || ((istr_reg & USB_ISTR_LS_DCONN) != 0U)) {
_hcd_port.connected = false;
_hcd_data.connected = false;
hcd_event_device_remove(rhport, in_isr);
}
}
}
//--------------------------------------------------------------------+
// Interrupt Helper Functions
//--------------------------------------------------------------------+
// Handle ACK response
static void ch_handle_ack(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) {
uint8_t const ep_num = ch_reg & USB_EPADDR_FIELD;
@ -271,38 +297,40 @@ static void ch_handle_ack(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) {
if (dir == TUSB_DIR_OUT) {
// OUT/TX direction
if (edpt->buflen != channel->queued_len[TUSB_DIR_OUT]) {
if (edpt->buflen != edpt->queued_len) {
// More data to send
uint16_t const len = tu_min16(edpt->buflen - channel->queued_len[TUSB_DIR_OUT], edpt->max_packet_size);
uint16_t const len = tu_min16(edpt->buflen - edpt->queued_len, edpt->max_packet_size);
uint16_t pma_addr = (uint16_t) btable_get_addr(ch_id, BTABLE_BUF_TX);
fsdev_write_packet_memory(pma_addr, &(edpt->buffer[channel->queued_len[TUSB_DIR_OUT]]), len);
fsdev_write_packet_memory(pma_addr, &(edpt->buffer[edpt->queued_len]), len);
btable_set_count(ch_id, BTABLE_BUF_TX, len);
channel->queued_len[TUSB_DIR_OUT] += len;
edpt->queued_len += len;
channel_write_status(ch_id, ch_reg, TUSB_DIR_OUT, EP_STAT_VALID, false);
channel->out.nak = 0;
} else {
// Transfer complete
channel_dealloc(channel, TUSB_DIR_OUT);
edpt->pid = (ch_reg & USB_CHEP_DTOG_TX) ? 1 : 0;
hcd_event_xfer_complete(daddr, ep_num, channel->queued_len[TUSB_DIR_OUT], XFER_RESULT_SUCCESS, true);
hcd_event_xfer_complete(daddr, ep_num, edpt->queued_len, XFER_RESULT_SUCCESS, true);
}
} else {
// IN/RX direction
uint16_t const rx_count = channel_get_rx_count(ch_id);
uint16_t pma_addr = (uint16_t) btable_get_addr(ch_id, BTABLE_BUF_RX);
fsdev_read_packet_memory(edpt->buffer + channel->queued_len[TUSB_DIR_IN], pma_addr, rx_count);
channel->queued_len[TUSB_DIR_IN] += rx_count;
fsdev_read_packet_memory(edpt->buffer + edpt->queued_len, pma_addr, rx_count);
edpt->queued_len += rx_count;
if ((rx_count < edpt->max_packet_size) || (channel->queued_len[TUSB_DIR_IN] >= edpt->buflen)) {
if ((rx_count < edpt->max_packet_size) || (edpt->queued_len >= edpt->buflen)) {
// Transfer complete (short packet or all bytes received)
channel_dealloc(channel, TUSB_DIR_IN);
edpt->pid = (ch_reg & USB_CHEP_DTOG_RX) ? 1 : 0;
hcd_event_xfer_complete(daddr, ep_num | TUSB_DIR_IN_MASK, channel->queued_len[TUSB_DIR_IN], XFER_RESULT_SUCCESS, true);
hcd_event_xfer_complete(daddr, ep_num | TUSB_DIR_IN_MASK, edpt->queued_len, XFER_RESULT_SUCCESS, true);
} else {
// More data expected
uint16_t const cnt = tu_min16(edpt->buflen - channel->queued_len[TUSB_DIR_IN], edpt->max_packet_size);
uint16_t const cnt = tu_min16(edpt->buflen - edpt->queued_len, edpt->max_packet_size);
btable_set_rx_bufsize(ch_id, BTABLE_BUF_RX, cnt);
channel_write_status(ch_id, ch_reg, TUSB_DIR_IN, EP_STAT_VALID, false);
channel->in.nak = 0;
}
}
}
@ -316,10 +344,17 @@ static void ch_handle_nak(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) {
if (ep_id == TUSB_INDEX_INVALID_8) return;
hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id];
// Retry non-periodic transfer immediately,
// Retry non-periodic transfer immediately if NAK count not exceeded
// Periodic transfer will be retried by next frame automatically
if (edpt->ep_type == TUSB_XFER_CONTROL || edpt->ep_type == TUSB_XFER_BULK) {
channel_write_status(ch_id, ch_reg, dir, EP_STAT_VALID, false);
hcd_channel_dir_t* channel_dir =
(dir == TUSB_DIR_OUT) ? &(_hcd_data.channel[ch_id].out) : &(_hcd_data.channel[ch_id].in);
if (channel_dir->nak < HCD_XFER_NAK_MAX) {
channel_dir->nak++;
}
if (channel_dir->nak < _tuh_cfg.max_nak || _tuh_cfg.max_nak == 0) {
channel_write_status(ch_id, ch_reg, dir, EP_STAT_VALID, false);
}
}
}
@ -328,13 +363,17 @@ static void ch_handle_stall(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) {
uint8_t const ep_num = ch_reg & USB_EPADDR_FIELD;
uint8_t const daddr = (ch_reg & USB_CHEP_DEVADDR_Msk) >> USB_CHEP_DEVADDR_Pos;
uint8_t ep_id = endpoint_find(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0));
if (ep_id == TUSB_INDEX_INVALID_8) return;
hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id];
hcd_channel_t* channel = &_hcd_data.channel[ch_id];
channel_dealloc(channel, dir);
channel_write_status(ch_id, ch_reg, dir, EP_STAT_DISABLED, false);
hcd_event_xfer_complete(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0),
channel->queued_len[dir], XFER_RESULT_STALLED, true);
edpt->queued_len, XFER_RESULT_STALLED, true);
}
// Handle error response
@ -345,21 +384,24 @@ static void ch_handle_error(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) {
uint8_t ep_id = endpoint_find(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0));
if (ep_id == TUSB_INDEX_INVALID_8) return;
hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id];
hcd_channel_t* channel = &_hcd_data.channel[ch_id];
ch_reg &= USB_EPREG_MASK | CH_STAT_MASK(dir);
ch_reg &= ~(dir == TUSB_DIR_OUT ? USB_CH_ERRTX : USB_CH_ERRRX);
if (channel->retry[dir] < HCD_XFER_ERROR_MAX) {
hcd_channel_dir_t* channel_dir =
(dir == TUSB_DIR_OUT) ? &(_hcd_data.channel[ch_id].out) : &(_hcd_data.channel[ch_id].in);
if (channel_dir->retry < HCD_XFER_ERROR_MAX) {
// Retry
channel->retry[dir]++;
channel_dir->retry++;
ch_change_status(&ch_reg, dir, EP_STAT_VALID);
} else {
// Failed after retries
channel_dealloc(channel, dir);
ch_change_status(&ch_reg, dir, EP_STAT_DISABLED);
hcd_event_xfer_complete(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0),
channel->queued_len[dir], XFER_RESULT_FAILED, true);
edpt->queued_len, XFER_RESULT_FAILED, true);
}
ch_write(ch_id, ch_reg, false);
}
@ -368,7 +410,7 @@ static void ch_handle_error(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) {
static inline void handle_ctr_tx(uint32_t ch_id) {
uint32_t ch_reg = ch_read(ch_id) | USB_EP_CTR_TX | USB_EP_CTR_RX;
hcd_channel_t* channel = &_hcd_data.channel[ch_id];
TU_VERIFY(channel->allocated[TUSB_DIR_OUT] == 1,);
TU_VERIFY(channel->out.allocated == 1,);
if ((ch_reg & USB_CH_ERRTX) == 0U) {
// No error
@ -388,7 +430,7 @@ static inline void handle_ctr_tx(uint32_t ch_id) {
static inline void handle_ctr_rx(uint32_t ch_id) {
uint32_t ch_reg = ch_read(ch_id) | USB_EP_CTR_TX | USB_EP_CTR_RX;
hcd_channel_t* channel = &_hcd_data.channel[ch_id];
TU_VERIFY(channel->allocated[TUSB_DIR_IN] == 1,);
TU_VERIFY(channel->in.allocated == 1,);
if ((ch_reg & USB_CH_ERRRX) == 0U) {
// No error
@ -408,7 +450,13 @@ static inline void handle_ctr_rx(uint32_t ch_id) {
void hcd_int_handler(uint8_t rhport, bool in_isr) {
uint32_t int_status = FSDEV_REG->ISTR;
/* Port Change Detected (Connection/Disconnection) */
// Start of Frame
if (int_status & USB_ISTR_SOF) {
FSDEV_REG->ISTR = (fsdev_bus_t)~USB_ISTR_SOF;
sof_handler();
}
// Port Change Detected (Connection/Disconnection)
if (int_status & USB_ISTR_DCON) {
FSDEV_REG->ISTR = (fsdev_bus_t)~USB_ISTR_DCON;
port_status_handler(rhport, in_isr);
@ -464,7 +512,7 @@ uint32_t hcd_frame_number(uint8_t rhport) {
// Get the current connect status of roothub port
bool hcd_port_connect_status(uint8_t rhport) {
(void) rhport;
return _hcd_port.connected;
return _hcd_data.connected;
}
// Reset USB bus on the port
@ -525,7 +573,7 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const
edpt->max_packet_size = packet_size;
edpt->interval = ep_desc->bInterval;
edpt->pid = 0;
edpt->low_speed = (hcd_port_speed_get(rhport) == TUSB_SPEED_FULL && tuh_speed_get(dev_addr) == TUSB_SPEED_LOW) ? 1 : 0;
edpt->ls_pre = (hcd_port_speed_get(rhport) == TUSB_SPEED_FULL && tuh_speed_get(dev_addr) == TUSB_SPEED_LOW) ? 1 : 0;
// EP0 is bi-directional, so we need to open both OUT and IN channels
if (ep_addr == 0) {
@ -574,6 +622,7 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b
edpt->buffer = buffer;
edpt->buflen = buflen;
edpt->queued_len = 0;
return edpt_xfer_kickoff(ep_id);
}
@ -588,8 +637,9 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) {
hcd_channel_t* channel = &_hcd_data.channel[i];
uint8_t const allocated = (dir == TUSB_DIR_OUT) ? channel->out.allocated : channel->in.allocated;
if (channel->allocated[dir] == 1 &&
if (allocated == 1 &&
channel->dev_addr == dev_addr &&
channel->ep_num == tu_edpt_number(ep_addr)) {
channel_dealloc(channel, dir);
@ -664,11 +714,11 @@ static void edpoint_close(uint8_t ep_id) {
for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) {
hcd_channel_t* channel = &_hcd_data.channel[i];
uint32_t ch_reg = ch_read(i) | USB_EP_CTR_TX | USB_EP_CTR_RX;
if (channel->allocated[TUSB_DIR_OUT] == 1 && channel->edpt[TUSB_DIR_OUT] == edpt) {
if (channel->out.allocated == 1 && channel->out.edpt == edpt) {
channel_dealloc(channel, TUSB_DIR_OUT);
channel_write_status(i, ch_reg, TUSB_DIR_OUT, EP_STAT_DISABLED, true);
}
if (channel->allocated[TUSB_DIR_IN] == 1 && channel->edpt[TUSB_DIR_IN] == edpt) {
if (channel->in.allocated == 1 && channel->in.edpt == edpt) {
channel_dealloc(channel, TUSB_DIR_IN);
channel_write_status(i, ch_reg, TUSB_DIR_IN, EP_STAT_DISABLED, true);
}
@ -697,27 +747,37 @@ static uint8_t channel_alloc(uint8_t dev_addr, uint8_t ep_addr, uint8_t ep_type)
// Find channel allocate for same ep_num but other direction
tusb_dir_t const other_dir = (dir == TUSB_DIR_IN) ? TUSB_DIR_OUT : TUSB_DIR_IN;
for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) {
if (_hcd_data.channel[i].allocated[dir] == 0 &&
_hcd_data.channel[i].allocated[other_dir] == 1 &&
uint8_t const allocated_dir = (dir == TUSB_DIR_OUT) ? _hcd_data.channel[i].out.allocated : _hcd_data.channel[i].in.allocated;
uint8_t const allocated_other = (other_dir == TUSB_DIR_OUT) ? _hcd_data.channel[i].out.allocated : _hcd_data.channel[i].in.allocated;
if (allocated_dir == 0 &&
allocated_other == 1 &&
_hcd_data.channel[i].dev_addr == dev_addr &&
_hcd_data.channel[i].ep_num == ep_num &&
_hcd_data.channel[i].ep_type == ep_type) {
_hcd_data.channel[i].allocated[dir] = 1;
_hcd_data.channel[i].queued_len[dir] = 0;
_hcd_data.channel[i].retry[dir] = 0;
if (dir == TUSB_DIR_OUT) {
_hcd_data.channel[i].out.allocated = 1;
_hcd_data.channel[i].out.retry = 0;
} else {
_hcd_data.channel[i].in.allocated = 1;
_hcd_data.channel[i].in.retry = 0;
}
return i;
}
}
// Find free channel
for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) {
if (_hcd_data.channel[i].allocated[0] == 0 && _hcd_data.channel[i].allocated[1] == 0) {
if (_hcd_data.channel[i].out.allocated == 0 && _hcd_data.channel[i].in.allocated == 0) {
_hcd_data.channel[i].dev_addr = dev_addr;
_hcd_data.channel[i].ep_num = ep_num;
_hcd_data.channel[i].ep_type = ep_type;
_hcd_data.channel[i].allocated[dir] = 1;
_hcd_data.channel[i].queued_len[dir] = 0;
_hcd_data.channel[i].retry[dir] = 0;
if (dir == TUSB_DIR_OUT) {
_hcd_data.channel[i].out.allocated = 1;
_hcd_data.channel[i].out.retry = 0;
} else {
_hcd_data.channel[i].in.allocated = 1;
_hcd_data.channel[i].in.retry = 0;
}
return i;
}
}
@ -733,16 +793,19 @@ static bool edpt_xfer_kickoff(uint8_t ep_id) {
TU_ASSERT(ch_id != TUSB_INDEX_INVALID_8); // all channel are in used
tusb_dir_t const dir = tu_edpt_dir(edpt->ep_addr);
hcd_channel_t* channel = &_hcd_data.channel[ch_id];
channel->edpt[dir] = edpt;
if (dir == TUSB_DIR_OUT) {
channel->out.edpt = edpt;
} else {
channel->in.edpt = edpt;
}
return channel_xfer_start(ch_id, dir);
}
static bool channel_xfer_start(uint8_t ch_id, tusb_dir_t dir) {
hcd_channel_t* channel = &_hcd_data.channel[ch_id];
hcd_endpoint_t* edpt = channel->edpt[dir];
hcd_endpoint_t* edpt = (dir == TUSB_DIR_OUT) ? channel->out.edpt : channel->in.edpt;
uint32_t ch_reg = ch_read(ch_id) & ~USB_EPREG_MASK;
ch_reg |= tu_edpt_number(edpt->ep_addr) | edpt->dev_addr << USB_CHEP_DEVADDR_Pos |
@ -771,28 +834,28 @@ static bool channel_xfer_start(uint8_t ch_id, tusb_dir_t dir) {
btable_set_addr(ch_id, dir == TUSB_DIR_OUT ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr);
if (dir == TUSB_DIR_OUT) {
uint16_t const len = tu_min16(edpt->buflen - channel->queued_len[TUSB_DIR_OUT], edpt->max_packet_size);
uint16_t const len = tu_min16(edpt->buflen - edpt->queued_len, edpt->max_packet_size);
fsdev_write_packet_memory(pma_addr, &(edpt->buffer[channel->queued_len[TUSB_DIR_OUT]]), len);
fsdev_write_packet_memory(pma_addr, &(edpt->buffer[edpt->queued_len]), len);
btable_set_count(ch_id, BTABLE_BUF_TX, len);
channel->queued_len[TUSB_DIR_OUT] += len;
edpt->queued_len += len;
} else {
btable_set_rx_bufsize(ch_id, BTABLE_BUF_RX, edpt->max_packet_size);
}
if (edpt->low_speed == 1) {
if (edpt->ls_pre == 1) {
ch_reg |= USB_CHEP_LSEP;
} else {
ch_reg &= ~USB_CHEP_LSEP;
}
// Setup DATA/STATUS phase start with DATA1
if (tu_edpt_number(edpt->ep_addr) == 0) {
edpt->pid = 1;
}
if (edpt->next_setup) {
// Setup packet uses IN token
edpt->next_setup = false;
ch_reg |= USB_EP_SETUP;
edpt->pid = 0;