diff options
| author | hathach <[email protected]> | 2026-03-11 13:34:59 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-03-11 13:34:59 +0700 |
| commit | 336e89792f9b572c5087b1df479d4e89542f6360 (patch) | |
| tree | 866a0acbf5dbc952b183768f2b916ff9fe36a591 /src | |
| parent | 60154a128795bcd6ca36042cac1b8bebf1a2764e (diff) | |
| parent | 7249c65b2a5b995ff7c2b45c1171f218b9d71112 (diff) | |
Merge branch 'master' into ep0_direct
# Conflicts:
# hw/bsp/same7x/boards/same70_qmtech/board.cmake
# hw/bsp/same7x/boards/same70_xplained/board.cmake
# hw/bsp/same7x/family.cmake
Diffstat (limited to 'src')
78 files changed, 4198 insertions, 1834 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 48dc75e50..00f466007 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ function(tinyusb_sources_get OUTPUT_VAR) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/mtp/mtp_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ecm_rndis_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ncm_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/printer/printer_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/usbtmc/usbtmc_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/vendor/vendor_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/video/video_device.c diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index f19c4a327..8f6dd7200 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -127,7 +127,7 @@ static bool acm_set_control_line_state(cdch_interface_t *p_cdc, tuh_xfer_cb_ static uint16_t const ftdi_vid_pid_list[][2] = {CFG_TUH_CDC_FTDI_VID_PID_LIST}; static uint16_t ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); -static bool ftdi_proccess_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static bool ftdi_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); static void ftdi_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); static bool ftdi_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); static bool ftdi_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); @@ -216,82 +216,84 @@ typedef struct { #define DRIVER_NAME_DECLARE(_str) #endif +// clang-format off // Note driver list must be in the same order as SERIAL_DRIVER enum static const cdch_serial_driver_t serial_drivers[] = { { - .vid_pid_list = NULL, - .vid_pid_count = 0, - .open = acm_open, - .process_set_config = acm_process_set_config, - .request_complete = acm_internal_control_complete, - .set_control_line_state = acm_set_control_line_state, - .set_baudrate = acm_set_line_coding, - .set_data_format = acm_set_line_coding, - .set_line_coding = acm_set_line_coding, - DRIVER_NAME_DECLARE("ACM") + .vid_pid_list = NULL, + .vid_pid_count = 0, + .open = acm_open, + .process_set_config = acm_process_set_config, + .request_complete = acm_internal_control_complete, + .set_control_line_state = acm_set_control_line_state, + .set_baudrate = acm_set_line_coding, + .set_data_format = acm_set_line_coding, + .set_line_coding = acm_set_line_coding, + DRIVER_NAME_DECLARE("ACM") }, #if CFG_TUH_CDC_FTDI { - .vid_pid_list = ftdi_vid_pid_list, - .vid_pid_count = TU_ARRAY_SIZE(ftdi_vid_pid_list), - .open = ftdi_open, - .process_set_config = ftdi_proccess_set_config, - .request_complete = ftdi_internal_control_complete, - .set_control_line_state = ftdi_set_modem_ctrl, - .set_baudrate = ftdi_set_baudrate, - .set_data_format = ftdi_set_data_format, - .set_line_coding = NULL, // 2 stage set line coding - DRIVER_NAME_DECLARE("FTDI") + .vid_pid_list = ftdi_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(ftdi_vid_pid_list), + .open = ftdi_open, + .process_set_config = ftdi_process_set_config, + .request_complete = ftdi_internal_control_complete, + .set_control_line_state = ftdi_set_modem_ctrl, + .set_baudrate = ftdi_set_baudrate, + .set_data_format = ftdi_set_data_format, + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("FTDI") }, #endif #if CFG_TUH_CDC_CP210X { - .vid_pid_list = cp210x_vid_pid_list, - .vid_pid_count = TU_ARRAY_SIZE(cp210x_vid_pid_list), - .open = cp210x_open, - .process_set_config = cp210x_process_set_config, - .request_complete = cp210x_internal_control_complete, - .set_control_line_state = cp210x_set_modem_ctrl, - .set_baudrate = cp210x_set_baudrate, - .set_data_format = cp210x_set_data_format, - .set_line_coding = NULL, // 2 stage set line coding - DRIVER_NAME_DECLARE("CP210x") + .vid_pid_list = cp210x_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(cp210x_vid_pid_list), + .open = cp210x_open, + .process_set_config = cp210x_process_set_config, + .request_complete = cp210x_internal_control_complete, + .set_control_line_state = cp210x_set_modem_ctrl, + .set_baudrate = cp210x_set_baudrate, + .set_data_format = cp210x_set_data_format, + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("CP210x") }, #endif #if CFG_TUH_CDC_CH34X { - .vid_pid_list = ch34x_vid_pid_list, - .vid_pid_count = TU_ARRAY_SIZE(ch34x_vid_pid_list), - .open = ch34x_open, - .process_set_config = ch34x_process_set_config, - .request_complete = ch34x_internal_control_complete, + .vid_pid_list = ch34x_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(ch34x_vid_pid_list), + .open = ch34x_open, + .process_set_config = ch34x_process_set_config, + .request_complete = ch34x_internal_control_complete, - .set_control_line_state = ch34x_set_modem_ctrl, - .set_baudrate = ch34x_set_baudrate, - .set_data_format = ch34x_set_data_format, - .set_line_coding = NULL, // 2 stage set line coding - DRIVER_NAME_DECLARE("CH34x") + .set_control_line_state = ch34x_set_modem_ctrl, + .set_baudrate = ch34x_set_baudrate, + .set_data_format = ch34x_set_data_format, + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("CH34x") }, #endif #if CFG_TUH_CDC_PL2303 { - .vid_pid_list = pl2303_vid_pid_list, - .vid_pid_count = TU_ARRAY_SIZE(pl2303_vid_pid_list), - .open = pl2303_open, - .process_set_config = pl2303_process_set_config, - .request_complete = pl2303_internal_control_complete, - .set_control_line_state = pl2303_set_modem_ctrl, - .set_baudrate = pl2303_set_line_coding, - .set_data_format = pl2303_set_line_coding, - .set_line_coding = pl2303_set_line_coding, - DRIVER_NAME_DECLARE("PL2303") + .vid_pid_list = pl2303_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(pl2303_vid_pid_list), + .open = pl2303_open, + .process_set_config = pl2303_process_set_config, + .request_complete = pl2303_internal_control_complete, + .set_control_line_state = pl2303_set_modem_ctrl, + .set_baudrate = pl2303_set_line_coding, + .set_data_format = pl2303_set_line_coding, + .set_line_coding = pl2303_set_line_coding, + DRIVER_NAME_DECLARE("PL2303") } #endif }; +// clang-format on TU_VERIFY_STATIC(TU_ARRAY_SIZE(serial_drivers) == SERIAL_DRIVER_COUNT, "Serial driver count mismatch"); @@ -761,7 +763,8 @@ uint16_t cdch_open(uint8_t rhport, uint8_t daddr, const tusb_desc_interface_t *i for (size_t i = 0; i < driver->vid_pid_count; i++) { if (driver->vid_pid_list[i][0] == vid && driver->vid_pid_list[i][1] == pid) { const uint16_t drv_len = driver->open(daddr, itf_desc, max_len); - TU_LOG_DRV("[:%u:%u] CDCh %s open %s\r\n", daddr, itf_desc->bInterfaceNumber, driver->name, drv_len > 0 ? "OK" : "FAILED"); + TU_LOG_DRV("[:%u:%u] CDCh %s open %s\r\n", daddr, itf_desc->bInterfaceNumber, driver->name, + drv_len > 0 ? "OK" : "FAILED"); return drv_len; } } @@ -773,35 +776,16 @@ uint16_t cdch_open(uint8_t rhport, uint8_t daddr, const tusb_desc_interface_t *i return 0; } -bool cdch_set_config(uint8_t daddr, uint8_t itf_num) { - tusb_control_request_t request; - request.wIndex = tu_htole16((uint16_t) itf_num); - uint8_t const idx = tuh_cdc_itf_get_index(daddr, itf_num); - cdch_interface_t *p_cdc = get_itf(idx); - TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); - TU_LOG_CDC(p_cdc, "set config"); - - // fake transfer to kick-off process_set_config() - tuh_xfer_t xfer; - xfer.daddr = daddr; - xfer.result = XFER_RESULT_SUCCESS; - xfer.setup = &request; - xfer.user_data = 0; // initial state 0 - cdch_process_set_config(&xfer); - - return true; -} - static void set_config_complete(cdch_interface_t *p_cdc, bool success) { if (success) { const uint8_t idx = get_idx_by_ptr(p_cdc); - p_cdc->mounted = true; + p_cdc->mounted = true; tuh_cdc_mount_cb(idx); // Prepare for incoming data tu_edpt_stream_read_xfer(&p_cdc->stream.rx); } else { // clear the interface entry - p_cdc->daddr = 0; + p_cdc->daddr = 0; p_cdc->bInterfaceNumber = 0; } @@ -810,6 +794,33 @@ static void set_config_complete(cdch_interface_t *p_cdc, bool success) { usbh_driver_set_config_complete(p_cdc->daddr, p_cdc->bInterfaceNumber + itf_offset); } +bool cdch_set_config(uint8_t daddr, uint8_t itf_num) { + const uint8_t idx = tuh_cdc_itf_get_index(daddr, itf_num); + cdch_interface_t *p_cdc = get_itf(idx); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set config"); + + // fake transfer to kick-off process_set_config() + tusb_control_request_t request; + request.wIndex = tu_htole16((uint16_t)itf_num); + + tuh_xfer_t xfer; + xfer.daddr = daddr; + xfer.ep_addr = 0; + xfer.result = XFER_RESULT_SUCCESS; + xfer.setup = &request; + xfer.complete_cb = NULL; + xfer.buffer = NULL; + xfer.user_data = 0; // initial state 0 + + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + if (!driver->process_set_config(p_cdc, &xfer)) { + set_config_complete(p_cdc, false); + } + + return true; +} + static void cdch_process_set_config(tuh_xfer_t *xfer) { cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); @@ -1215,22 +1226,14 @@ static uint16_t ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, return drv_len; } -static bool ftdi_proccess_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { +static bool ftdi_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); const uintptr_t state = xfer->user_data; switch (state) { // from here sequence overtaken from Linux Kernel function ftdi_port_probe() case CONFIG_FTDI_DETERMINE_TYPE: // determine type - if (p_cdc->bInterfaceNumber == 0) { - TU_ASSERT(ftdi_determine_type(p_cdc)); - } else { - // other interfaces have same type as interface 0 - uint8_t const idx_itf0 = tuh_cdc_itf_get_index(xfer->daddr, 0); - cdch_interface_t const *p_cdc_itf0 = get_itf(idx_itf0); - TU_ASSERT(p_cdc_itf0); - p_cdc->ftdi.chip_type = p_cdc_itf0->ftdi.chip_type; - } + TU_ASSERT(ftdi_determine_type(p_cdc)); TU_ATTR_FALLTHROUGH; case CONFIG_FTDI_WRITE_LATENCY: diff --git a/src/class/dfu/dfu_device.c b/src/class/dfu/dfu_device.c index 36f9ffcf4..65332eae3 100644 --- a/src/class/dfu/dfu_device.c +++ b/src/class/dfu/dfu_device.c @@ -338,7 +338,7 @@ bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control default: if (stage == CONTROL_STAGE_SETUP) { - return reply_getstatus(rhport, request, _dfu_ctx.state, _dfu_ctx.status, 0); + return reply_getstatus(rhport, request, (dfu_state_t) _dfu_ctx.state, (dfu_status_t) _dfu_ctx.status, 0); } break; } @@ -387,7 +387,7 @@ static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tus timeout = 0; } - return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout); + return reply_getstatus(rhport, request, next_state, (dfu_status_t) _dfu_ctx.status, timeout); } else if (stage == CONTROL_STAGE_ACK) { if (_dfu_ctx.flashing_in_progress) { _dfu_ctx.state = DFU_DNBUSY; @@ -416,7 +416,7 @@ static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, const tus timeout = 0; } - return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout); + return reply_getstatus(rhport, request, next_state, (dfu_status_t) _dfu_ctx.status, timeout); } else if (stage == CONTROL_STAGE_ACK) { if (_dfu_ctx.flashing_in_progress) { _dfu_ctx.state = DFU_MANIFEST; diff --git a/src/class/midi/midi_device.c b/src/class/midi/midi_device.c index 023a81595..a49cd725b 100644 --- a/src/class/midi/midi_device.c +++ b/src/class/midi/midi_device.c @@ -168,6 +168,108 @@ uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, ui return total_read; } +// Note: this function shares stream->buffer with tud_midi_n_stream_read(). +// Do not mix calls to both functions on the same interface. +uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize) { + TU_VERIFY(p_cable_num != NULL && buffer != NULL && bufsize > 0, 0); + + midid_interface_t *p_midi = &_midid_itf[itf]; + midi_driver_stream_t *stream = &p_midi->stream_read; + tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx; + + uint8_t *buf8 = (uint8_t *)buffer; + uint32_t total_read = 0; + + // Initialize to invalid cable so callers can detect "no data" even when + // the return value is 0. + *p_cable_num = 0xff; + + // If there are leftover bytes from a previous partial read, return them first + if (stream->total > 0) { + *p_cable_num = (stream->buffer[0] >> 4) & 0x0f; + const uint8_t count = (uint8_t)tu_min32((uint32_t)(stream->total - stream->index), bufsize); + TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1 + stream->index, count)); + + total_read += count; + stream->index += count; + buf8 += count; + bufsize -= count; + + if (stream->total == stream->index) { + stream->index = 0; + stream->total = 0; + } + + if (bufsize == 0) { + return total_read; + } + } + + while (bufsize > 0) { + // Peek at next packet header to get cable number without consuming + uint8_t one_byte; + if (!tu_edpt_stream_peek(ep_str, &one_byte)) { + break; + } + + const uint8_t next_cable = (one_byte >> 4) & 0x0f; + + // Stop if cable changed (covers both leftover-originated reads and + // freshly consumed packets — total_read > 0 in either case) + if (total_read > 0 && next_cable != *p_cable_num) { + break; + } + *p_cable_num = next_cable; + + // Consume the packet + if (!tud_midi_n_packet_read(itf, stream->buffer)) { + break; + } + + const uint8_t code_index = stream->buffer[0] & 0x0f; + uint8_t msg_bytes; + + // MIDI 1.0 Table 4-1: Code Index Number Classifications + switch (code_index) { + case MIDI_CIN_MISC: + case MIDI_CIN_CABLE_EVENT: + // Reserved and unused, skip this packet + continue; + + case MIDI_CIN_SYSEX_END_1BYTE: + case MIDI_CIN_1BYTE_DATA: + msg_bytes = 1; + break; + + case MIDI_CIN_SYSCOM_2BYTE: + case MIDI_CIN_SYSEX_END_2BYTE: + case MIDI_CIN_PROGRAM_CHANGE: + case MIDI_CIN_CHANNEL_PRESSURE: + msg_bytes = 2; + break; + + default: + msg_bytes = 3; + break; + } + + const uint8_t count = (uint8_t)tu_min32((uint32_t)msg_bytes, bufsize); + TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1, count)); + + total_read += count; + buf8 += count; + bufsize -= count; + + if (count < msg_bytes) { + // Output buffer full, save remaining for next call + stream->total = msg_bytes; + stream->index = count; + } + } + + return total_read; +} + bool tud_midi_n_packet_read(uint8_t itf, uint8_t packet[4]) { midid_interface_t *p_midi = &_midid_itf[itf]; tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx; diff --git a/src/class/midi/midi_device.h b/src/class/midi/midi_device.h index ddbc2f9f0..b80ad544a 100644 --- a/src/class/midi/midi_device.h +++ b/src/class/midi/midi_device.h @@ -66,6 +66,13 @@ uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num); // Read byte stream (legacy) uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, uint32_t bufsize); +// Read byte stream with cable demultiplexing: returns the cable number of the +// data that was read. Reads from a single cable per call; stops when the next +// packet belongs to a different cable so the caller can dispatch per-cable. +// Note: shares internal state with tud_midi_n_stream_read(); do not mix both +// on the same interface. +uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize); + // Write byte Stream (legacy) uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize); @@ -97,6 +104,11 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_stream_read(void *buffer, } TU_ATTR_ALWAYS_INLINE static inline uint32_t +tud_midi_demux_stream_read(uint8_t *p_cable_num, void *buffer, uint32_t bufsize) { + return tud_midi_n_demux_stream_read(0, p_cable_num, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_stream_write(uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize) { return tud_midi_n_stream_write(0, cable_num, buffer, bufsize); } diff --git a/src/class/msc/msc_device.c b/src/class/msc/msc_device.c index 15bfafc35..3766e3a25 100644 --- a/src/class/msc/msc_device.c +++ b/src/class/msc/msc_device.c @@ -646,7 +646,11 @@ bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t break; } - TU_ASSERT(prepare_cbw(p_msc)); + if (!usbd_edpt_stalled(rhport, p_msc->ep_out)) { + TU_ASSERT(prepare_cbw(p_msc)); + } else { + p_msc->stage = MSC_STAGE_CMD; + } } else { // Any xfer ended here is considered unknown error, ignore it TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n"); diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 4942a105a..59096e476 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -92,8 +92,9 @@ typedef struct { uint8_t itf_num; uint8_t ep_in; uint8_t ep_out; - uint8_t ep_event; + uint8_t ep_event; + uint8_t ep_sz_fs; // Bulk Only Transfer (BOT) Protocol uint8_t phase; @@ -194,42 +195,47 @@ static bool prepare_new_command(mtpd_interface_t* p_mtp) { return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_out, _mtpd_epbuf.buf, CFG_TUD_MTP_EP_BUFSIZE, false); } -static bool mtpd_data_xfer(mtp_container_info_t* p_container, uint8_t ep_addr) { - mtpd_interface_t* p_mtp = &_mtpd_itf; +bool tud_mtp_data_send(mtp_container_info_t *p_container) { + mtpd_interface_t *p_mtp = &_mtpd_itf; if (p_mtp->phase == MTP_PHASE_COMMAND) { // 1st data block: header + payload p_mtp->phase = MTP_PHASE_DATA; p_mtp->xferred_len = 0; + p_mtp->total_len = p_container->header->len; - if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { - p_mtp->total_len = p_container->header->len; - p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->header->transaction_id = p_mtp->command.header.transaction_id; - p_mtp->io_header = *p_container->header; // save header for subsequent data - } else { - // OUT transfer: total length is at least max packet size - p_mtp->total_len = tu_max32(p_container->header->len, CFG_TUD_MTP_EP_BUFSIZE); - } - } else { - // subsequent data block: payload only - TU_ASSERT(p_mtp->phase == MTP_PHASE_DATA); + p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->header->transaction_id = p_mtp->command.header.transaction_id; + p_mtp->io_header = *p_container->header; // save header for subsequent data } - const uint16_t xact_len = (uint16_t) tu_min32(p_mtp->total_len - p_mtp->xferred_len, CFG_TUD_MTP_EP_BUFSIZE); + const uint16_t xact_len = (uint16_t)tu_min32(p_mtp->total_len - p_mtp->xferred_len, CFG_TUD_MTP_EP_BUFSIZE); + + TU_LOG_DRV(" MTP Data IN: xferred_len/total_len=%lu/%lu, xact_len=%u\r\n", p_mtp->xferred_len, p_mtp->total_len, + xact_len); if (xact_len) { - // already transferred all bytes in header's length. Application make an unnecessary extra call - TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr)); - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, _mtpd_epbuf.buf, xact_len, false)); + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_in)); + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, xact_len, false)); } return true; } -bool tud_mtp_data_send(mtp_container_info_t* p_container) { - return mtpd_data_xfer(p_container, _mtpd_itf.ep_in); -} +bool tud_mtp_data_receive(mtp_container_info_t *p_container) { + mtpd_interface_t *p_mtp = &_mtpd_itf; + if (p_mtp->phase == MTP_PHASE_COMMAND) { + // 1st data block: header + payload + p_mtp->phase = MTP_PHASE_DATA; + p_mtp->xferred_len = 0; + p_mtp->total_len = p_container->header->len; + } -bool tud_mtp_data_receive(mtp_container_info_t* p_container) { - return mtpd_data_xfer(p_container, _mtpd_itf.ep_out); + // up to buffer size since 1st packet (with header) may also contain payload + const uint16_t xact_len = CFG_TUD_MTP_EP_BUFSIZE; + + TU_LOG_DRV(" MTP Data OUT: xferred_len/total_len=%lu/%lu, xact_len=%u\r\n", p_mtp->xferred_len, p_mtp->total_len, + xact_len); + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_out)); + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_out, _mtpd_epbuf.buf, xact_len, false)); + return true; } bool tud_mtp_response_send(mtp_container_info_t* p_container) { @@ -287,15 +293,20 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16 p_mtp->itf_num = itf_desc->bInterfaceNumber; // Open interrupt IN endpoint - const tusb_desc_endpoint_t* ep_desc = (const tusb_desc_endpoint_t*) tu_desc_next(itf_desc); - TU_ASSERT(ep_desc->bDescriptorType == TUSB_DESC_ENDPOINT && ep_desc->bmAttributes.xfer == TUSB_XFER_INTERRUPT, 0); - TU_ASSERT(usbd_edpt_open(rhport, ep_desc), 0); - p_mtp->ep_event = ep_desc->bEndpointAddress; + const tusb_desc_endpoint_t* ep_desc_int = (const tusb_desc_endpoint_t*) tu_desc_next(itf_desc); + TU_ASSERT(ep_desc_int->bDescriptorType == TUSB_DESC_ENDPOINT && ep_desc_int->bmAttributes.xfer == TUSB_XFER_INTERRUPT, 0); + TU_ASSERT(usbd_edpt_open(rhport, ep_desc_int), 0); + p_mtp->ep_event = ep_desc_int->bEndpointAddress; // Open endpoint pair - TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(ep_desc), 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0); + const tusb_desc_endpoint_t* ep_desc_bulk = (const tusb_desc_endpoint_t*) tu_desc_next(ep_desc_int); + TU_ASSERT(usbd_open_edpt_pair(rhport, (const uint8_t*)ep_desc_bulk, 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0); TU_ASSERT(prepare_new_command(p_mtp), 0); + if (tud_speed_get() == TUSB_SPEED_FULL) { + p_mtp->ep_sz_fs = (uint8_t)tu_edpt_packet_size(ep_desc_bulk); + } + return mtpd_itf_size; } @@ -377,8 +388,8 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t mtp_generic_container_t* p_container = (mtp_generic_container_t*) _mtpd_epbuf.buf; #if CFG_TUSB_DEBUG >= CFG_TUD_MTP_LOG_LEVEL - tu_lookup_find(&_mtp_op_table, p_mtp->command.header.code); - TU_LOG_DRV(" MTP %s: %s phase\r\n", (const char *) tu_lookup_find(&_mtp_op_table, p_mtp->command.header.code), + const uint16_t code = (p_mtp->phase == MTP_PHASE_COMMAND) ? p_container->header.code : p_mtp->command.header.code; + TU_LOG_DRV(" MTP %s: %s phase\r\n", (const char *) tu_lookup_find(&_mtp_op_table, code), _mtp_phase_str[p_mtp->phase]); #endif @@ -417,19 +428,35 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t } case MTP_PHASE_DATA: { - const uint16_t bulk_mps = (tud_speed_get() == TUSB_SPEED_HIGH) ? 512 : 64; p_mtp->xferred_len += xferred_bytes; cb_data.total_xferred_bytes = p_mtp->xferred_len; - bool is_complete = false; - // complete if ZLP or short packet or total length reached - if (xferred_bytes == 0 || // ZLP - (xferred_bytes & (bulk_mps - 1)) || // short packet - p_mtp->xferred_len >= p_mtp->total_len) { // total length reached - is_complete = true; + const bool is_data_in = (ep_addr == p_mtp->ep_in); + // For IN endpoint, threshold is bulk max packet size + // For OUT endpoint, threshold is endpoint buffer size, since we always queue fixed size + uint16_t threshold; + if (is_data_in) { + threshold = (p_mtp->ep_sz_fs > 0) ? p_mtp->ep_sz_fs : 512; // full speed bulk if set + } else { + threshold = CFG_TUD_MTP_EP_BUFSIZE; + } + + // Check completion: ZLP, short packet, or total length reached + const bool is_complete = + (xferred_bytes == 0 || xferred_bytes < threshold || p_mtp->xferred_len >= p_mtp->total_len); + + TU_LOG_DRV(" MTP Data %s CB: xferred_bytes=%lu, xferred_len/total_len=%lu/%lu, is_complete=%d\r\n", + is_data_in ? "IN" : "OUT", xferred_bytes, p_mtp->xferred_len, p_mtp->total_len, is_complete ? 1 : 0); + + // Send/queue ZLP if packet is full-sized but transfer is complete + if (is_complete && xferred_bytes > 0 && !(xferred_bytes & (threshold - 1))) { + TU_LOG_DRV(" queue ZLP\r\n"); + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr)); + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, NULL, 0, false)); + return true; } - if (ep_addr == p_mtp->ep_in) { + if (is_data_in) { // Data In if (is_complete) { cb_data.io_container.header->len = sizeof(mtp_container_header_t); diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index a33f1dc08..6cce7efbb 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -18,7 +18,7 @@ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN0 + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * This file is part of the TinyUSB stack. diff --git a/src/class/printer/printer.h b/src/class/printer/printer.h new file mode 100644 index 000000000..09d1a8956 --- /dev/null +++ b/src/class/printer/printer.h @@ -0,0 +1,62 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_PRINTER_H_ +#define TUSB_PRINTER_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Printer Class Specific Control Request +typedef enum { + TUSB_PRINTER_REQUEST_GET_DEVICE_ID = 0x00, ///< Get device ID + TUSB_PRINTER_REQUEST_GET_PORT_STATUS = 0x01, ///< Get port status + TUSB_PRINTER_REQUEST_SOFT_RESET = 0x02, ///< Soft reset +} tusb_printer_request_type_t; + +/// Printer Port Status (returned by GET_PORT_STATUS request) +/// USB Printer Class spec 1.1, Section 4.2 +typedef union TU_ATTR_PACKED { + uint8_t status; + struct TU_ATTR_PACKED { + uint8_t reserved0 : 3; ///< Reserved (bits 0-2) + uint8_t not_error : 1; ///< 1 = no error, 0 = error + uint8_t selected : 1; ///< 1 = selected (online), 0 = not selected + uint8_t paper_empty : 1; ///< 1 = paper empty, 0 = paper not empty + uint8_t reserved6 : 2; ///< Reserved (bits 6-7) + } status_bm; +} tusb_printer_port_status_t; + +TU_VERIFY_STATIC(sizeof(tusb_printer_port_status_t) == 1, "size is not correct"); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/class/printer/printer_device.c b/src/class/printer/printer_device.c new file mode 100644 index 000000000..f5bb33795 --- /dev/null +++ b/src/class/printer/printer_device.c @@ -0,0 +1,329 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_PRINTER) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "printer_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +typedef struct { + uint8_t itf_num; + + /*------------- From this point, data is not cleared by bus reset -------------*/ + + tu_edpt_stream_t rx_stream; + tu_edpt_stream_t tx_stream; + + uint8_t rx_ff_buf[CFG_TUD_PRINTER_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUD_PRINTER_TX_BUFSIZE]; +} printer_interface_t; + +#define ITF_MEM_RESET_SIZE offsetof(printer_interface_t, rx_stream) + +#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 +typedef struct { + TUD_EPBUF_DEF(epout, CFG_TUD_PRINTER_EP_BUFSIZE); + TUD_EPBUF_DEF(epin, CFG_TUD_PRINTER_EP_BUFSIZE); +} printer_epbuf_t; + +CFG_TUD_MEM_SECTION static printer_epbuf_t _printer_epbuf[CFG_TUD_PRINTER]; +#endif + +static printer_interface_t _printer_itf[CFG_TUD_PRINTER]; + +//--------------------------------------------------------------------+ +// INTERNAL HELPERS +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline uint8_t _find_itf(uint8_t ep_addr) { + for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) { + const printer_interface_t *p = &_printer_itf[i]; + if (ep_addr == p->rx_stream.ep_addr || ep_addr == p->tx_stream.ep_addr) { + return i; + } + } + return TUSB_INDEX_INVALID_8; +} + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_printer_rx_cb(uint8_t itf) { + (void)itf; +} + +TU_ATTR_WEAK void tud_printer_tx_complete_cb(uint8_t itf) { + (void)itf; +} + +TU_ATTR_WEAK void tud_printer_request_complete_cb(uint8_t itf, tusb_control_request_t const *request) { + (void)itf; + (void)request; +} + +TU_ATTR_WEAK uint8_t const *tud_printer_get_device_id_cb(uint8_t itf) { + (void)itf; + return NULL; +} + +TU_ATTR_WEAK uint8_t tud_printer_get_port_status_cb(uint8_t itf) { + (void)itf; + return 0x18; // not error, selected, paper not empty +} + +TU_ATTR_WEAK void tud_printer_soft_reset_cb(uint8_t itf) { + (void)itf; +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ +uint32_t tud_printer_n_read_available(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_PRINTER, 0); + return tu_edpt_stream_read_available(&_printer_itf[itf].rx_stream); +} + +uint32_t tud_printer_n_read(uint8_t itf, void *buffer, uint32_t bufsize) { + TU_VERIFY(itf < CFG_TUD_PRINTER, 0); + return tu_edpt_stream_read(&_printer_itf[itf].rx_stream, buffer, bufsize); +} + +bool tud_printer_n_peek(uint8_t itf, uint8_t *chr) { + TU_VERIFY(itf < CFG_TUD_PRINTER); + return tu_edpt_stream_peek(&_printer_itf[itf].rx_stream, chr); +} + +void tud_printer_n_read_flush(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_PRINTER, ); + printer_interface_t *p = &_printer_itf[itf]; + tu_edpt_stream_clear(&p->rx_stream); + tu_edpt_stream_read_xfer(&p->rx_stream); +} + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ +uint32_t tud_printer_n_write(uint8_t itf, const void *buffer, uint32_t bufsize) { + TU_VERIFY(itf < CFG_TUD_PRINTER, 0); + return tu_edpt_stream_write(&_printer_itf[itf].tx_stream, buffer, bufsize); +} + +uint32_t tud_printer_n_write_flush(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_PRINTER, 0); + return tu_edpt_stream_write_xfer(&_printer_itf[itf].tx_stream); +} + +uint32_t tud_printer_n_write_available(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_PRINTER, 0); + return tu_edpt_stream_write_available(&_printer_itf[itf].tx_stream); +} + +bool tud_printer_n_write_clear(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_PRINTER); + tu_edpt_stream_clear(&_printer_itf[itf].tx_stream); + return true; +} + +//--------------------------------------------------------------------+ +// USBD-CLASS API +//--------------------------------------------------------------------+ +void printerd_init(void) { + tu_memclr(_printer_itf, sizeof(_printer_itf)); + + for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) { + printer_interface_t *p = &_printer_itf[i]; + + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + uint8_t *epout_buf = NULL; + uint8_t *epin_buf = NULL; + #else + uint8_t *epout_buf = _printer_epbuf[i].epout; + uint8_t *epin_buf = _printer_epbuf[i].epin; + #endif + + tu_edpt_stream_init(&p->rx_stream, false, false, false, + p->rx_ff_buf, CFG_TUD_PRINTER_RX_BUFSIZE, + epout_buf, CFG_TUD_PRINTER_EP_BUFSIZE); + + tu_edpt_stream_init(&p->tx_stream, false, true, true, + p->tx_ff_buf, CFG_TUD_PRINTER_TX_BUFSIZE, + epin_buf, CFG_TUD_PRINTER_EP_BUFSIZE); + } +} + +bool printerd_deinit(void) { + for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) { + printer_interface_t *p = &_printer_itf[i]; + tu_edpt_stream_deinit(&p->rx_stream); + tu_edpt_stream_deinit(&p->tx_stream); + } + return true; +} + +void printerd_reset(uint8_t rhport) { + (void)rhport; + + for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) { + printer_interface_t *p = &_printer_itf[i]; + tu_memclr(p, ITF_MEM_RESET_SIZE); + tu_edpt_stream_close(&p->rx_stream); + tu_edpt_stream_close(&p->tx_stream); + } +} + +uint16_t printerd_open(uint8_t rhport, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + TU_VERIFY(TUSB_CLASS_PRINTER == itf_desc->bInterfaceClass, 0); + + // Find available interface slot + uint8_t const printer_id = _find_itf(0); + TU_ASSERT(printer_id < CFG_TUD_PRINTER, 0); + printer_interface_t *p = &_printer_itf[printer_id]; + + p->itf_num = itf_desc->bInterfaceNumber; + + //------------- Endpoints -------------// + const uint8_t *p_desc = (const uint8_t *)itf_desc; + const uint8_t *desc_end = p_desc + max_len; + uint16_t drv_len = sizeof(tusb_desc_interface_t); + + p_desc = tu_desc_next(itf_desc); + for (uint8_t e = 0; e < itf_desc->bNumEndpoints; e++) { + TU_VERIFY(tu_desc_in_bounds(p_desc, desc_end), 0); + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, 0); + + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + tu_edpt_stream_open(&p->tx_stream, rhport, desc_ep); + tu_edpt_stream_clear(&p->tx_stream); + } else { + tu_edpt_stream_open(&p->rx_stream, rhport, desc_ep); + tu_edpt_stream_clear(&p->rx_stream); + TU_ASSERT(tu_edpt_stream_read_xfer(&p->rx_stream) > 0, 0); + } + + drv_len += sizeof(tusb_desc_endpoint_t); + p_desc = tu_desc_next(p_desc); + } + + return drv_len; +} + +bool printerd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request) { + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE && + request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + // GET_DEVICE_ID: wIndex = (interface_number << 8) | alt_setting + // GET_PORT_STATUS / SOFT_RESET: wIndex = interface_number + uint8_t itf_num; + if (TUSB_PRINTER_REQUEST_GET_DEVICE_ID == request->bRequest) { + itf_num = tu_u16_high(request->wIndex); + } else { + itf_num = tu_u16_low(request->wIndex); + } + + // Find the printer instance index from the USB interface number + uint8_t itf = TUSB_INDEX_INVALID_8; + for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) { + if (_printer_itf[i].itf_num == itf_num) { + itf = i; + break; + } + } + TU_VERIFY(itf < CFG_TUD_PRINTER); + + // https://www.usb.org/sites/default/files/usbprint11a021811.pdf + if (stage == CONTROL_STAGE_SETUP) { + switch (request->bRequest) { + case TUSB_PRINTER_REQUEST_GET_DEVICE_ID: { + const uint8_t *device_id = tud_printer_get_device_id_cb(itf); + TU_VERIFY(device_id); + const uint16_t total_len = (uint16_t)((device_id[0] << 8) | device_id[1]); + return tud_control_xfer(rhport, request, (void *)(uintptr_t)device_id, total_len); + } + + case TUSB_PRINTER_REQUEST_GET_PORT_STATUS: { + static uint8_t port_status; + port_status = tud_printer_get_port_status_cb(itf); + return tud_control_xfer(rhport, request, &port_status, sizeof(port_status)); + } + + case TUSB_PRINTER_REQUEST_SOFT_RESET: + tud_printer_soft_reset_cb(itf); + tud_control_status(rhport, request); + return true; + + default: + return false; + } + } else if (stage == CONTROL_STAGE_ACK) { + tud_printer_request_complete_cb(itf, request); + } + + return true; +} + +bool printerd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void)rhport; + (void)result; + + uint8_t const itf = _find_itf(ep_addr); + TU_ASSERT(itf < CFG_TUD_PRINTER); + printer_interface_t *p = &_printer_itf[itf]; + + // Received new data + if (ep_addr == p->rx_stream.ep_addr) { + tu_edpt_stream_read_xfer_complete(&p->rx_stream, xferred_bytes); + + if (!tu_edpt_stream_empty(&p->rx_stream)) { + tud_printer_rx_cb(itf); + } + + tu_edpt_stream_read_xfer(&p->rx_stream); + } + + // Data sent to host + if (ep_addr == p->tx_stream.ep_addr) { + tud_printer_tx_complete_cb(itf); + + if (0 == tu_edpt_stream_write_xfer(&p->tx_stream)) { + tu_edpt_stream_write_zlp_if_needed(&p->tx_stream, xferred_bytes); + } + } + + return true; +} + +#endif diff --git a/src/class/printer/printer_device.h b/src/class/printer/printer_device.h new file mode 100644 index 000000000..a6b7052cb --- /dev/null +++ b/src/class/printer/printer_device.h @@ -0,0 +1,140 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_PRINTER_DEVICE_H_ +#define TUSB_PRINTER_DEVICE_H_ + +#include "printer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Application API (Multiple Ports) i.e. CFG_TUD_PRINTER > 1 +//--------------------------------------------------------------------+ + +// Get the number of bytes available for reading +uint32_t tud_printer_n_read_available(uint8_t itf); + +// Read received bytes +uint32_t tud_printer_n_read(uint8_t itf, void *buffer, uint32_t bufsize); + +// Get the number of bytes available for writing +uint32_t tud_printer_n_write_available(uint8_t itf); + +// Clear the received FIFO +void tud_printer_n_read_flush(uint8_t itf); + +// Get a byte from FIFO without removing it +bool tud_printer_n_peek(uint8_t itf, uint8_t *ui8); + +// Write data to host +uint32_t tud_printer_n_write(uint8_t itf, const void *buffer, uint32_t bufsize); + +// Force sending data in the TX FIFO +uint32_t tud_printer_n_write_flush(uint8_t itf); + +// Clear the transmit FIFO +bool tud_printer_n_write_clear(uint8_t itf); + +//--------------------------------------------------------------------+ +// Application API (Single Port) +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_read_available(void) { + return tud_printer_n_read_available(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write_available(void) { + return tud_printer_n_write_available(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_read(void *buffer, uint32_t bufsize) { + return tud_printer_n_read(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline void tud_printer_read_flush(void) { + tud_printer_n_read_flush(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_printer_peek(uint8_t *ui8) { + return tud_printer_n_peek(0, ui8); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write(const void *buffer, uint32_t bufsize) { + return tud_printer_n_write(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write_flush(void) { + return tud_printer_n_write_flush(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_printer_write_clear(void) { + return tud_printer_n_write_clear(0); +} + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when received new data +void tud_printer_rx_cb(uint8_t itf); + +// Invoked when last write transfer is completed +void tud_printer_tx_complete_cb(uint8_t itf); + +// Invoked when host requests device ID string (IEEE 1284). +// Application returns pointer to device ID buffer (must remain valid until transfer completes). +// First 2 bytes of returned buffer must contain big-endian length (including the 2 length bytes). +const uint8_t *tud_printer_get_device_id_cb(uint8_t itf); + +// Invoked when host requests port status. +uint8_t tud_printer_get_port_status_cb(uint8_t itf); + +// Invoked when host requests soft reset. +void tud_printer_soft_reset_cb(uint8_t itf); + +// Invoked when a control request is completed (GET_DEVICE_ID, GET_PORT_STATUS, etc.) +void tud_printer_request_complete_cb(uint8_t itf, tusb_control_request_t const *request); + + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void printerd_init(void); +bool printerd_deinit(void); +void printerd_reset(uint8_t rhport); +uint16_t printerd_open(uint8_t rhport, const tusb_desc_interface_t *itf_desc, uint16_t max_len); +bool printerd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request); +bool printerd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/class/vendor/vendor_device.c b/src/class/vendor/vendor_device.c index b8e6fec6f..b917c8dc7 100644 --- a/src/class/vendor/vendor_device.c +++ b/src/class/vendor/vendor_device.c @@ -40,36 +40,33 @@ typedef struct { uint8_t rhport; uint8_t itf_num; + #if CFG_TUD_VENDOR_TXRX_BUFFERED /*------------- From this point, data is not cleared by bus reset -------------*/ - struct { - tu_edpt_stream_t tx; - tu_edpt_stream_t rx; - - #if CFG_TUD_VENDOR_TX_BUFSIZE > 0 - uint8_t tx_ff_buf[CFG_TUD_VENDOR_TX_BUFSIZE]; - #endif - - #if CFG_TUD_VENDOR_RX_BUFSIZE > 0 - uint8_t rx_ff_buf[CFG_TUD_VENDOR_RX_BUFSIZE]; + tu_edpt_stream_t tx_stream; + tu_edpt_stream_t rx_stream; + uint8_t tx_ff_buf[CFG_TUD_VENDOR_TX_BUFSIZE]; + uint8_t rx_ff_buf[CFG_TUD_VENDOR_RX_BUFSIZE]; + #else + uint8_t ep_in; + uint8_t ep_out; + uint16_t ep_in_mps; + uint16_t ep_out_mps; #endif - } stream; } vendord_interface_t; -#define ITF_MEM_RESET_SIZE (offsetof(vendord_interface_t, itf_num) + sizeof(((vendord_interface_t *)0)->itf_num)) + #if CFG_TUD_VENDOR_TXRX_BUFFERED + #define ITF_MEM_RESET_SIZE (offsetof(vendord_interface_t, itf_num) + TU_FIELD_SIZE(vendord_interface_t, itf_num)) + #else + #define ITF_MEM_RESET_SIZE sizeof(vendord_interface_t) + #endif static vendord_interface_t _vendord_itf[CFG_TUD_VENDOR]; -#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 || CFG_TUD_VENDOR_RX_BUFSIZE == 0 + // Skip local EP buffer if dedicated hw FIFO is supported or no fifo mode + #if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 || !CFG_TUD_VENDOR_TXRX_BUFFERED typedef struct { - // Skip local EP buffer if dedicated hw FIFO is supported - #if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 || CFG_TUD_VENDOR_RX_BUFSIZE == 0 TUD_EPBUF_DEF(epout, CFG_TUD_VENDOR_EPSIZE); - #endif - - // Skip local EP buffer if dedicated hw FIFO is supported - #if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 TUD_EPBUF_DEF(epin, CFG_TUD_VENDOR_EPSIZE); - #endif } vendord_epbuf_t; CFG_TUD_MEM_SECTION static vendord_epbuf_t _vendord_epbuf[CFG_TUD_VENDOR]; @@ -78,7 +75,6 @@ CFG_TUD_MEM_SECTION static vendord_epbuf_t _vendord_epbuf[CFG_TUD_VENDOR]; //--------------------------------------------------------------------+ // Weak stubs: invoked if no strong implementation is available //--------------------------------------------------------------------+ - TU_ATTR_WEAK void tud_vendor_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize) { (void)idx; (void)buffer; @@ -93,40 +89,44 @@ TU_ATTR_WEAK void tud_vendor_tx_cb(uint8_t idx, uint32_t sent_bytes) { //-------------------------------------------------------------------- // Application API //-------------------------------------------------------------------- - bool tud_vendor_n_mounted(uint8_t idx) { TU_VERIFY(idx < CFG_TUD_VENDOR); vendord_interface_t *p_itf = &_vendord_itf[idx]; - return p_itf->stream.rx.ep_addr || p_itf->stream.tx.ep_addr; + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + return (p_itf->rx_stream.ep_addr != 0) || (p_itf->tx_stream.ep_addr != 0); + #else + return (p_itf->ep_out != 0) || (p_itf->ep_in != 0); + #endif } //--------------------------------------------------------------------+ // Read API //--------------------------------------------------------------------+ -#if CFG_TUD_VENDOR_RX_BUFSIZE > 0 + #if CFG_TUD_VENDOR_TXRX_BUFFERED uint32_t tud_vendor_n_available(uint8_t idx) { TU_VERIFY(idx < CFG_TUD_VENDOR, 0); vendord_interface_t *p_itf = &_vendord_itf[idx]; - return tu_edpt_stream_read_available(&p_itf->stream.rx); + return tu_edpt_stream_read_available(&p_itf->rx_stream); } bool tud_vendor_n_peek(uint8_t idx, uint8_t *u8) { TU_VERIFY(idx < CFG_TUD_VENDOR); vendord_interface_t *p_itf = &_vendord_itf[idx]; - return tu_edpt_stream_peek(&p_itf->stream.rx, u8); + return tu_edpt_stream_peek(&p_itf->rx_stream, u8); } uint32_t tud_vendor_n_read(uint8_t idx, void *buffer, uint32_t bufsize) { TU_VERIFY(idx < CFG_TUD_VENDOR, 0); vendord_interface_t *p_itf = &_vendord_itf[idx]; - return tu_edpt_stream_read(&p_itf->stream.rx, buffer, bufsize); + return tu_edpt_stream_read(&p_itf->rx_stream, buffer, bufsize); } void tud_vendor_n_read_flush(uint8_t idx) { TU_VERIFY(idx < CFG_TUD_VENDOR, ); vendord_interface_t *p_itf = &_vendord_itf[idx]; - tu_edpt_stream_clear(&p_itf->stream.rx); - tu_edpt_stream_read_xfer(&p_itf->stream.rx); + tu_edpt_stream_clear(&p_itf->rx_stream); + tu_edpt_stream_read_xfer(&p_itf->rx_stream); } #endif @@ -134,9 +134,17 @@ void tud_vendor_n_read_flush(uint8_t idx) { bool tud_vendor_n_read_xfer(uint8_t idx) { TU_VERIFY(idx < CFG_TUD_VENDOR); vendord_interface_t *p_itf = &_vendord_itf[idx]; - return tu_edpt_stream_read_xfer(&p_itf->stream.rx); + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + return tu_edpt_stream_read_xfer(&p_itf->rx_stream); + + #else + // Non-FIFO mode + TU_VERIFY(usbd_edpt_claim(p_itf->rhport, p_itf->ep_out)); + return usbd_edpt_xfer(p_itf->rhport, p_itf->ep_out, _vendord_epbuf[idx].epout, CFG_TUD_VENDOR_EPSIZE, false); + #endif } -#endif + #endif //--------------------------------------------------------------------+ @@ -145,26 +153,45 @@ bool tud_vendor_n_read_xfer(uint8_t idx) { uint32_t tud_vendor_n_write(uint8_t idx, const void *buffer, uint32_t bufsize) { TU_VERIFY(idx < CFG_TUD_VENDOR, 0); vendord_interface_t *p_itf = &_vendord_itf[idx]; - return tu_edpt_stream_write(&p_itf->stream.tx, buffer, (uint16_t)bufsize); + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + return tu_edpt_stream_write(&p_itf->tx_stream, buffer, (uint16_t)bufsize); + + #else + // non-fifo mode: direct transfer + TU_VERIFY(usbd_edpt_claim(p_itf->rhport, p_itf->ep_in), 0); + const uint32_t xact_len = tu_min32(bufsize, CFG_TUD_VENDOR_EPSIZE); + memcpy(_vendord_epbuf[idx].epin, buffer, xact_len); + TU_ASSERT(usbd_edpt_xfer(p_itf->rhport, p_itf->ep_in, _vendord_epbuf[idx].epin, (uint16_t)xact_len, false), 0); + return xact_len; + #endif } -#if CFG_TUD_VENDOR_TX_BUFSIZE > 0 -uint32_t tud_vendor_n_write_flush(uint8_t idx) { +uint32_t tud_vendor_n_write_available(uint8_t idx) { TU_VERIFY(idx < CFG_TUD_VENDOR, 0); vendord_interface_t *p_itf = &_vendord_itf[idx]; - return tu_edpt_stream_write_xfer(&p_itf->stream.tx); + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + return tu_edpt_stream_write_available(&p_itf->tx_stream); + + #else + // Non-FIFO mode + TU_VERIFY(p_itf->ep_in > 0, 0); // must be opened + return usbd_edpt_busy(p_itf->rhport, p_itf->ep_in) ? 0 : CFG_TUD_VENDOR_EPSIZE; + #endif } -uint32_t tud_vendor_n_write_available(uint8_t idx) { + #if CFG_TUD_VENDOR_TXRX_BUFFERED +uint32_t tud_vendor_n_write_flush(uint8_t idx) { TU_VERIFY(idx < CFG_TUD_VENDOR, 0); vendord_interface_t *p_itf = &_vendord_itf[idx]; - return tu_edpt_stream_write_available(&p_itf->stream.tx); + return tu_edpt_stream_write_xfer(&p_itf->tx_stream); } bool tud_vendor_n_write_clear(uint8_t idx) { TU_VERIFY(idx < CFG_TUD_VENDOR, 0); vendord_interface_t *p_itf = &_vendord_itf[idx]; - tu_edpt_stream_clear(&p_itf->stream.tx); + tu_edpt_stream_clear(&p_itf->tx_stream); return true; } #endif @@ -175,48 +202,37 @@ bool tud_vendor_n_write_clear(uint8_t idx) { void vendord_init(void) { tu_memclr(_vendord_itf, sizeof(_vendord_itf)); - for(uint8_t i=0; i<CFG_TUD_VENDOR; i++) { - vendord_interface_t* p_itf = &_vendord_itf[i]; + #if CFG_TUD_VENDOR_TXRX_BUFFERED + for (uint8_t i = 0; i < CFG_TUD_VENDOR; i++) { + vendord_interface_t *p_itf = &_vendord_itf[i]; - #if CFG_TUD_EDPT_DEDICATED_HWFIFO - #if CFG_TUD_VENDOR_RX_BUFSIZE == 0 // non-fifo rx still need ep buffer - uint8_t *epout_buf = _vendord_epbuf[i].epout; - #else + #if CFG_TUD_EDPT_DEDICATED_HWFIFO uint8_t *epout_buf = NULL; - #endif - - uint8_t *epin_buf = NULL; - #else + uint8_t *epin_buf = NULL; + #else uint8_t *epout_buf = _vendord_epbuf[i].epout; uint8_t *epin_buf = _vendord_epbuf[i].epin; - #endif - - #if CFG_TUD_VENDOR_RX_BUFSIZE > 0 - uint8_t *rx_ff_buf = p_itf->stream.rx_ff_buf; - #else - uint8_t *rx_ff_buf = NULL; - #endif + #endif - tu_edpt_stream_init(&p_itf->stream.rx, false, false, false, rx_ff_buf, CFG_TUD_VENDOR_RX_BUFSIZE, epout_buf, + uint8_t *rx_ff_buf = p_itf->rx_ff_buf; + tu_edpt_stream_init(&p_itf->rx_stream, false, false, false, rx_ff_buf, CFG_TUD_VENDOR_RX_BUFSIZE, epout_buf, CFG_TUD_VENDOR_EPSIZE); - #if CFG_TUD_VENDOR_TX_BUFSIZE > 0 - uint8_t *tx_ff_buf = p_itf->stream.tx_ff_buf; - #else - uint8_t *tx_ff_buf = NULL; - #endif - - tu_edpt_stream_init(&p_itf->stream.tx, false, true, false, tx_ff_buf, CFG_TUD_VENDOR_TX_BUFSIZE, epin_buf, + uint8_t *tx_ff_buf = p_itf->tx_ff_buf; + tu_edpt_stream_init(&p_itf->tx_stream, false, true, false, tx_ff_buf, CFG_TUD_VENDOR_TX_BUFSIZE, epin_buf, CFG_TUD_VENDOR_EPSIZE); } + #endif } bool vendord_deinit(void) { - for(uint8_t i=0; i<CFG_TUD_VENDOR; i++) { - vendord_interface_t* p_itf = &_vendord_itf[i]; - tu_edpt_stream_deinit(&p_itf->stream.rx); - tu_edpt_stream_deinit(&p_itf->stream.tx); + #if CFG_TUD_VENDOR_TXRX_BUFFERED + for (uint8_t i = 0; i < CFG_TUD_VENDOR; i++) { + vendord_interface_t *p_itf = &_vendord_itf[i]; + tu_edpt_stream_deinit(&p_itf->rx_stream); + tu_edpt_stream_deinit(&p_itf->tx_stream); } + #endif return true; } @@ -227,11 +243,12 @@ void vendord_reset(uint8_t rhport) { vendord_interface_t* p_itf = &_vendord_itf[i]; tu_memclr(p_itf, ITF_MEM_RESET_SIZE); - tu_edpt_stream_clear(&p_itf->stream.rx); - tu_edpt_stream_close(&p_itf->stream.rx); - - tu_edpt_stream_clear(&p_itf->stream.tx); - tu_edpt_stream_close(&p_itf->stream.tx); + #if CFG_TUD_VENDOR_TXRX_BUFFERED + tu_edpt_stream_clear(&p_itf->rx_stream); + tu_edpt_stream_close(&p_itf->rx_stream); + tu_edpt_stream_clear(&p_itf->tx_stream); + tu_edpt_stream_close(&p_itf->tx_stream); + #endif } } @@ -241,13 +258,25 @@ static uint8_t find_vendor_itf(uint8_t ep_addr) { const vendord_interface_t *p_vendor = &_vendord_itf[idx]; if (ep_addr == 0) { // find unused: require both ep == 0 - if (p_vendor->stream.rx.ep_addr == 0 && p_vendor->stream.tx.ep_addr == 0) { + #if CFG_TUD_VENDOR_TXRX_BUFFERED + if (p_vendor->rx_stream.ep_addr == 0 && p_vendor->tx_stream.ep_addr == 0) { return idx; } - } else if (ep_addr == p_vendor->stream.rx.ep_addr || ep_addr == p_vendor->stream.tx.ep_addr) { - return idx; + #else + if (p_vendor->ep_out == 0 && p_vendor->ep_in == 0) { + return idx; + } + #endif } else { - // nothing to do + #if CFG_TUD_VENDOR_TXRX_BUFFERED + if (ep_addr == p_vendor->rx_stream.ep_addr || ep_addr == p_vendor->tx_stream.ep_addr) { + return idx; + } + #else + if (ep_addr == p_vendor->ep_out || ep_addr == p_vendor->ep_in) { + return idx; + } + #endif } } return 0xff; @@ -273,28 +302,39 @@ uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uin const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); - // open endpoint stream, skip if already opened (multiple IN/OUT endpoints) + #if CFG_TUD_VENDOR_TXRX_BUFFERED + // open endpoint stream if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { - tu_edpt_stream_t *stream_tx = &p_vendor->stream.tx; - if (stream_tx->ep_addr == 0) { - tu_edpt_stream_open(stream_tx, rhport, desc_ep); - tu_edpt_stream_write_xfer(stream_tx); // flush pending data - } + tu_edpt_stream_t *tx_stream = &p_vendor->tx_stream; + tu_edpt_stream_open(tx_stream, rhport, desc_ep); + tu_edpt_stream_write_xfer(tx_stream); // flush pending data } else { - tu_edpt_stream_t *stream_rx = &p_vendor->stream.rx; - if (stream_rx->ep_addr == 0) { - tu_edpt_stream_open(stream_rx, rhport, desc_ep); - #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 - TU_ASSERT(tu_edpt_stream_read_xfer(stream_rx) > 0, 0); // prepare for incoming data - #endif - } + tu_edpt_stream_t *rx_stream = &p_vendor->rx_stream; + tu_edpt_stream_open(rx_stream, rhport, desc_ep); + #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 + TU_ASSERT(tu_edpt_stream_read_xfer(rx_stream) > 0, 0); // prepare for incoming data + #endif + } + #else + // Non-FIFO mode: store endpoint info + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + p_vendor->ep_in = desc_ep->bEndpointAddress; + p_vendor->ep_in_mps = tu_edpt_packet_size(desc_ep); + } else { + p_vendor->ep_out = desc_ep->bEndpointAddress; + p_vendor->ep_out_mps = tu_edpt_packet_size(desc_ep); + #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 + // Prepare for incoming data + TU_ASSERT(usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, CFG_TUD_VENDOR_EPSIZE, false), 0); + #endif } + #endif } p_desc = tu_desc_next(p_desc); } - return (uint16_t) ((uintptr_t) p_desc - (uintptr_t) desc_itf); + return (uint16_t)((uintptr_t)p_desc - (uintptr_t)desc_itf); } bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { @@ -304,34 +344,36 @@ bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint TU_VERIFY(idx < CFG_TUD_VENDOR); vendord_interface_t *p_vendor = &_vendord_itf[idx]; - if (ep_addr == p_vendor->stream.rx.ep_addr) { - #if CFG_TUD_VENDOR_RX_BUFSIZE - // Received new data: put into stream's fifo - tu_edpt_stream_read_xfer_complete(&p_vendor->stream.rx, xferred_bytes); - #endif - - // invoke callback - #if CFG_TUD_VENDOR_RX_BUFSIZE == 0 - tud_vendor_rx_cb(idx, p_vendor->stream.rx.ep_buf, xferred_bytes); - #else +#if CFG_TUD_VENDOR_TXRX_BUFFERED + if (ep_addr == p_vendor->rx_stream.ep_addr) { + // Put received data to FIFO + tu_edpt_stream_read_xfer_complete(&p_vendor->rx_stream, xferred_bytes); tud_vendor_rx_cb(idx, NULL, 0); - #endif - - #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 - tu_edpt_stream_read_xfer(&p_vendor->stream.rx); // prepare next data - #endif - } else if (ep_addr == p_vendor->stream.tx.ep_addr) { + #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 + tu_edpt_stream_read_xfer(&p_vendor->rx_stream); // prepare next data + #endif + } else if (ep_addr == p_vendor->tx_stream.ep_addr) { // Send complete tud_vendor_tx_cb(idx, (uint16_t)xferred_bytes); - #if CFG_TUD_VENDOR_TX_BUFSIZE > 0 // try to send more if possible - if (0 == tu_edpt_stream_write_xfer(&p_vendor->stream.tx)) { + if (0 == tu_edpt_stream_write_xfer(&p_vendor->tx_stream)) { // If there is no data left, a ZLP should be sent if xferred_bytes is multiple of EP Packet size and not zero - tu_edpt_stream_write_zlp_if_needed(&p_vendor->stream.tx, xferred_bytes); + tu_edpt_stream_write_zlp_if_needed(&p_vendor->tx_stream, xferred_bytes); } - #endif } + #else + if (ep_addr == p_vendor->ep_out) { + // Non-FIFO mode: invoke callback with buffer + tud_vendor_rx_cb(idx, _vendord_epbuf[idx].epout, xferred_bytes); + #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 + usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, CFG_TUD_VENDOR_EPSIZE, false); + #endif + } else if (ep_addr == p_vendor->ep_in) { + // Send complete + tud_vendor_tx_cb(idx, (uint16_t)xferred_bytes); + } + #endif return true; } diff --git a/src/class/vendor/vendor_device.h b/src/class/vendor/vendor_device.h index c3de4c49d..101765bb1 100644 --- a/src/class/vendor/vendor_device.h +++ b/src/class/vendor/vendor_device.h @@ -50,8 +50,14 @@ extern "C" { #define CFG_TUD_VENDOR_TX_BUFSIZE 64 #endif +// Vendor is buffered (FIFO mode) if both TX and RX buffers are configured +// If either is 0, vendor operates in non-buffered (direct transfer) mode +#ifndef CFG_TUD_VENDOR_TXRX_BUFFERED + #define CFG_TUD_VENDOR_TXRX_BUFFERED ((CFG_TUD_VENDOR_RX_BUFSIZE > 0) && (CFG_TUD_VENDOR_TX_BUFSIZE > 0)) +#endif + // Application will manually schedule RX transfer. This can be useful when using with non-fifo (buffered) mode -// i.e. CFG_TUD_VENDOR_RX_BUFSIZE = 0 +// i.e. CFG_TUD_VENDOR_TXRX_BUFFERED = 0 #ifndef CFG_TUD_VENDOR_RX_MANUAL_XFER #define CFG_TUD_VENDOR_RX_MANUAL_XFER 0 #endif @@ -63,7 +69,8 @@ extern "C" { // Return whether the vendor interface is mounted bool tud_vendor_n_mounted(uint8_t idx); -#if CFG_TUD_VENDOR_RX_BUFSIZE > 0 +//------------- RX -------------// +#if CFG_TUD_VENDOR_TXRX_BUFFERED // Return number of available bytes for reading uint32_t tud_vendor_n_available(uint8_t idx); @@ -82,16 +89,17 @@ void tud_vendor_n_read_flush(uint8_t idx); bool tud_vendor_n_read_xfer(uint8_t idx); #endif +//------------- TX -------------// // Write to TX FIFO. This can be buffered and not sent immediately unless buffered bytes >= USB endpoint size uint32_t tud_vendor_n_write(uint8_t idx, const void *buffer, uint32_t bufsize); -#if CFG_TUD_VENDOR_TX_BUFSIZE > 0 +// Return number of bytes available for writing in TX FIFO (or endpoint if non-buffered) +uint32_t tud_vendor_n_write_available(uint8_t idx); + +#if CFG_TUD_VENDOR_TXRX_BUFFERED // Force sending buffered data, return number of bytes sent uint32_t tud_vendor_n_write_flush(uint8_t idx); -// Return number of bytes available for writing in TX FIFO -uint32_t tud_vendor_n_write_available(uint8_t idx); - // Clear the transmit FIFO bool tud_vendor_n_write_clear(uint8_t idx); #endif @@ -111,7 +119,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_mounted(void) { return tud_vendor_n_mounted(0); } -#if CFG_TUD_VENDOR_RX_BUFSIZE > 0 +#if CFG_TUD_VENDOR_TXRX_BUFFERED TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_available(void) { return tud_vendor_n_available(0); } @@ -127,6 +135,14 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_read(void *buffer, uint3 TU_ATTR_ALWAYS_INLINE static inline void tud_vendor_read_flush(void) { tud_vendor_n_read_flush(0); } + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_flush(void) { + return tud_vendor_n_write_flush(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_write_clear(void) { + return tud_vendor_n_write_clear(0); +} #endif #if CFG_TUD_VENDOR_RX_MANUAL_XFER @@ -143,20 +159,10 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_str(const char *st return tud_vendor_n_write_str(0, str); } -#if CFG_TUD_VENDOR_TX_BUFSIZE > 0 -TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_flush(void) { - return tud_vendor_n_write_flush(0); -} - TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_available(void) { return tud_vendor_n_write_available(0); } -TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_write_clear(void) { - return tud_vendor_n_write_clear(0); -} -#endif - // backward compatible #define tud_vendor_flush() tud_vendor_write_flush() @@ -165,8 +171,8 @@ TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_write_clear(void) { //--------------------------------------------------------------------+ // Invoked when received new data. -// - CFG_TUD_VENDOR_RX_BUFSIZE > 0; buffer and bufsize must not be used (both NULL,0) since data is in RX FIFO -// - CFG_TUD_VENDOR_RX_BUFSIZE = 0: Buffer and bufsize are valid +// - CFG_TUD_VENDOR_TXRX_BUFFERED = 1: buffer and bufsize must not be used (both NULL,0) since data is in RX FIFO +// - CFG_TUD_VENDOR_TXRX_BUFFERED = 0: Buffer and bufsize are valid void tud_vendor_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize); // Invoked when tx transfer is finished diff --git a/src/class/video/video_device.c b/src/class/video/video_device.c index b2375def9..bbcfe45d5 100644 --- a/src/class/video/video_device.c +++ b/src/class/video/video_device.c @@ -70,6 +70,13 @@ typedef struct TU_ATTR_PACKED { uint8_t bEntityId; } tusb_desc_cs_video_entity_itf_t; +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint16_t wMaxTransferSize; +} tusb_desc_cs_video_vc_ep_t; + typedef union { struct TU_ATTR_PACKED { uint8_t bLength; @@ -740,6 +747,9 @@ static bool _close_vc_itf(uint8_t rhport, videod_interface_t *self) /* The end of the video control interface descriptor. */ void const *end = _end_of_control_descriptor(vc); if (vc->std.bNumEndpoints != 0) { + /* Extend end to cover the standard endpoint and class-specific endpoint descriptors + * that follow wTotalLength */ + end = (uint8_t const*)end + sizeof(tusb_desc_endpoint_t) + sizeof(tusb_desc_cs_video_vc_ep_t); /* Find the notification endpoint descriptor. */ cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); TU_ASSERT(cur < end); @@ -780,6 +790,9 @@ static bool _open_vc_itf(uint8_t rhport, videod_interface_t *self, uint_fast8_t if (vc->std.bNumEndpoints != 0) { /* Support for 1 endpoint only. */ TU_VERIFY(1 == vc->std.bNumEndpoints); + /* Extend end to cover the standard endpoint and class-specific endpoint descriptors + * that follow wTotalLength */ + end = (uint8_t const*)end + sizeof(tusb_desc_endpoint_t) + sizeof(tusb_desc_cs_video_vc_ep_t); /* Find the notification endpoint descriptor. */ cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); TU_VERIFY(cur < end); diff --git a/src/class/video/video_device.h b/src/class/video/video_device.h index f14555e4f..2750bb2fb 100644 --- a/src/class/video/video_device.h +++ b/src/class/video/video_device.h @@ -99,8 +99,7 @@ int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, * @param[in] stm_idx Destination streaming interface index * @param[out] payload_buf Payload storage buffer (target buffer for requested data) * @param[in] payload_size Size of payload_buf (requested data size) - * @param[in] offset Current byte offset relative to given bufsize from tud_video_n_frame_xfer (framesize) - * @return video_error_code_t */ + * @param[in] offset Current byte offset relative to given bufsize from tud_video_n_frame_xfer (framesize) */ void tud_video_prepare_payload_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, tud_video_payload_request_t* request); //--------------------------------------------------------------------+ diff --git a/src/common/tusb_fifo.c b/src/common/tusb_fifo.c index a92435912..991ee18ff 100644 --- a/src/common/tusb_fifo.c +++ b/src/common/tusb_fifo.c @@ -30,11 +30,6 @@ #define TU_FIFO_DBG 0 -// Suppress IAR warning -// Warning[Pa082]: undefined behavior: the order of volatile accesses is undefined in this statement -#if defined(__ICCARM__) -#pragma diag_suppress = Pa082 -#endif #if OSAL_MUTEX_REQUIRED @@ -110,8 +105,9 @@ void tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable) { } //--------------------------------------------------------------------+ -// Pull & Push -// copy data to/from fifo without updating read/write pointers +// Hardware FIFO API +// Support different data access width and address increment scheme +// Can support multiple i.e both 16 and 32-bit data access if needed //--------------------------------------------------------------------+ #if CFG_TUSB_FIFO_HWFIFO_API #if CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE @@ -122,18 +118,31 @@ void tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable) { #define HWFIFO_ADDR_NEXT(_hwfifo, _const) HWFIFO_ADDR_NEXT_N(_hwfifo, _const, CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE) +//------------- Write -------------// #ifndef CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE -static inline void stride_write(volatile void *hwfifo, const void *src, uint8_t data_stride) { +TU_ATTR_ALWAYS_INLINE static inline void stride_write(volatile void *hwfifo, const void *src, uint8_t data_stride) { + (void)data_stride; // possible unused #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 4 - if (data_stride == 4) { + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 4 + if (data_stride == 4) + #endif + { *((volatile uint32_t *)hwfifo) = tu_unaligned_read32(src); } - #endif - #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 2 - if (data_stride == 2) { + #endif + + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 2 + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 2 + if (data_stride == 2) + #endif + { *((volatile uint16_t *)hwfifo) = tu_unaligned_read16(src); } - #endif + #endif + + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1 + *((volatile uint8_t *)hwfifo) = *(const uint8_t *)src; + #endif } // Copy from fifo to fixed address buffer (usually a tx register) with TU_FIFO_FIXED_ADDR_RW32 mode @@ -147,7 +156,8 @@ void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, co HWFIFO_ADDR_NEXT(hwfifo, ); } - #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE > 1 + #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS // 16-bit access is allowed for odd bytes if (len >= 2) { *((volatile uint16_t *)hwfifo) = tu_unaligned_read16(src); @@ -155,16 +165,16 @@ void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, co len -= 2; HWFIFO_ADDR_NEXT_N(hwfifo, , 2); } - #endif + #endif - #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS + #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS // 8-bit access is allowed for odd bytes while (len > 0) { *((volatile uint8_t *)hwfifo) = *src++; len--; HWFIFO_ADDR_NEXT_N(hwfifo, , 1); } - #else + #else // Write odd bytes i.e 1 byte for 16 bit or 1-3 bytes for 32 bit if (len > 0) { @@ -173,13 +183,16 @@ void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, co stride_write(hwfifo, &tmp, data_stride); HWFIFO_ADDR_NEXT(hwfifo, ); } + #endif #endif } #endif +//------------- Read -------------// #ifndef CFG_TUSB_FIFO_HWFIFO_CUSTOM_READ -static inline void stride_read(const volatile void *hwfifo, void *dest, uint8_t data_stride) { +TU_ATTR_ALWAYS_INLINE static inline void stride_read(const volatile void *hwfifo, void *dest, uint8_t data_stride) { (void)data_stride; // possible unused + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 4 #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 4 if (data_stride == 4) @@ -197,6 +210,10 @@ static inline void stride_read(const volatile void *hwfifo, void *dest, uint8_t tu_unaligned_write16(dest, *((const volatile uint16_t *)hwfifo)); } #endif + + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1 + *(uint8_t *)dest = *((const volatile uint8_t *)hwfifo); + #endif } void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, const tu_hwfifo_access_t *access_mode) { @@ -209,7 +226,8 @@ void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, co HWFIFO_ADDR_NEXT(hwfifo, const); } - #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE > 1 + #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS // 16-bit access is allowed for odd bytes if (len >= 2) { tu_unaligned_write16(dest, *((const volatile uint16_t *)hwfifo)); @@ -235,6 +253,7 @@ void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, co HWFIFO_ADDR_NEXT(hwfifo, const); } #endif + #endif } #endif @@ -251,13 +270,17 @@ static void hwff_push_n(const tu_fifo_t *f, const void *app_buf, uint16_t n, uin tu_hwfifo_read(hwfifo, ff_buf, n, access_mode); } else { // Wrap around case - + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1 + tu_hwfifo_read(hwfifo, ff_buf, lin_bytes, access_mode); // linear part + HWFIFO_ADDR_NEXT_N(hwfifo, const, lin_bytes); + tu_hwfifo_read(hwfifo, f->buffer, wrap_bytes, access_mode); // wrapped part + #else // Write full words to linear part of buffer const uint8_t data_stride = access_mode->data_stride; const uint32_t odd_mask = data_stride - 1; uint16_t lin_even = lin_bytes & ~odd_mask; tu_hwfifo_read(hwfifo, ff_buf, lin_even, access_mode); - HWFIFO_ADDR_NEXT_N(hwfifo, const, lin_even); + HWFIFO_ADDR_NEXT_N(hwfifo, const, (lin_even / data_stride) * CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE); ff_buf += lin_even; // There could be odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary @@ -286,6 +309,7 @@ static void hwff_push_n(const tu_fifo_t *f, const void *app_buf, uint16_t n, uin if (wrap_bytes > 0) { tu_hwfifo_read(hwfifo, ff_buf, wrap_bytes, access_mode); } + #endif } } @@ -303,13 +327,17 @@ static void hwff_pull_n(const tu_fifo_t *f, void *app_buf, uint16_t n, uint16_t tu_hwfifo_write(hwfifo, ff_buf, n, access_mode); } else { // Wrap around case - + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1 + tu_hwfifo_write(hwfifo, ff_buf, lin_bytes, access_mode); // linear part + HWFIFO_ADDR_NEXT_N(hwfifo, , lin_bytes); + tu_hwfifo_write(hwfifo, f->buffer, wrap_bytes, access_mode); // wrapped part + #else // Read full words from linear part const uint8_t data_stride = access_mode->data_stride; const uint32_t odd_mask = data_stride - 1; - uint16_t lin_even = lin_bytes & ~odd_mask; + uint16_t lin_even = lin_bytes & ~odd_mask; tu_hwfifo_write(hwfifo, ff_buf, lin_even, access_mode); - HWFIFO_ADDR_NEXT_N(hwfifo, , lin_even); + HWFIFO_ADDR_NEXT_N(hwfifo, , (lin_even / data_stride) * CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE); ff_buf += lin_even; // There could be odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary @@ -338,10 +366,15 @@ static void hwff_pull_n(const tu_fifo_t *f, void *app_buf, uint16_t n, uint16_t if (wrap_bytes > 0) { tu_hwfifo_write(hwfifo, ff_buf, wrap_bytes, access_mode); } + #endif } } #endif +//--------------------------------------------------------------------+ +// Pull & Push +// copy data to/from fifo without updating read/write pointers +//--------------------------------------------------------------------+ // send n items to fifo WITHOUT updating write pointer static void ff_push_n(const tu_fifo_t *f, const void *app_buf, uint16_t n, uint16_t wr_ptr) { uint16_t lin_bytes = f->depth - wr_ptr; @@ -458,7 +491,9 @@ uint16_t tu_fifo_peek_n_access_mode(tu_fifo_t *f, void *p_buffer, uint16_t n, ui // Read n items without removing it from the FIFO, correct read pointer if overflowed uint16_t tu_fifo_peek_n(tu_fifo_t *f, void *p_buffer, uint16_t n) { ff_lock(f->mutex_rd); - const uint16_t ret = tu_fifo_peek_n_access_mode(f, p_buffer, n, f->wr_idx, f->rd_idx, NULL); + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + const uint16_t ret = tu_fifo_peek_n_access_mode(f, p_buffer, n, wr_idx, rd_idx, NULL); ff_unlock(f->mutex_rd); return ret; } @@ -468,7 +503,8 @@ uint16_t tu_fifo_read_n_access_mode(tu_fifo_t *f, void *buffer, uint16_t n, cons ff_lock(f->mutex_rd); // Peek the data: f->rd_idx might get modified in case of an overflow so we can not use a local variable - n = tu_fifo_peek_n_access_mode(f, buffer, n, f->wr_idx, f->rd_idx, access_mode); + const uint16_t wr_idx = f->wr_idx; + n = tu_fifo_peek_n_access_mode(f, buffer, n, wr_idx, f->rd_idx, access_mode); f->rd_idx = advance_index(f->depth, f->rd_idx, n); ff_unlock(f->mutex_rd); @@ -595,7 +631,8 @@ static bool ff_peek_local(tu_fifo_t *f, void *buf, uint16_t wr_idx, uint16_t rd_ bool tu_fifo_read(tu_fifo_t *f, void *buffer) { // Peek the data // f->rd_idx might get modified in case of an overflow so we can not use a local variable - const bool ret = ff_peek_local(f, buffer, f->wr_idx, f->rd_idx); + const uint16_t wr_idx = f->wr_idx; + const bool ret = ff_peek_local(f, buffer, wr_idx, f->rd_idx); if (ret) { ff_lock(f->mutex_rd); f->rd_idx = advance_index(f->depth, f->rd_idx, 1); @@ -607,7 +644,9 @@ bool tu_fifo_read(tu_fifo_t *f, void *buffer) { // Read one item without removing it from the FIFO, correct read index if overflowed bool tu_fifo_peek(tu_fifo_t *f, void *p_buffer) { - return ff_peek_local(f, p_buffer, f->wr_idx, f->rd_idx); + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return ff_peek_local(f, p_buffer, wr_idx, rd_idx); } // Write one element into the buffer @@ -787,6 +826,6 @@ void tu_fifo_get_write_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info) { } else { info->linear.len = f->depth - wr_ptr; info->wrapped.len = remain - info->linear.len; // Remaining length - n already was limited to remain or FIFO depth - info->wrapped.ptr = f->buffer; // Always start of buffer + info->wrapped.ptr = f->buffer; // Always start of buffer } } diff --git a/src/common/tusb_fifo.h b/src/common/tusb_fifo.h index 86ba59059..b31a0802e 100644 --- a/src/common/tusb_fifo.h +++ b/src/common/tusb_fifo.h @@ -120,9 +120,9 @@ typedef struct { uint8_t *buffer; // buffer pointer uint16_t depth; // max items bool overwritable; // overwritable when full - // 1 byte padding here + // 1 byte padding here - volatile uint16_t wr_idx; // write index TODO maybe can drop volatile + volatile uint16_t wr_idx; // write index volatile uint16_t rd_idx; // read index #if OSAL_MUTEX_REQUIRED @@ -291,16 +291,22 @@ TU_ATTR_ALWAYS_INLINE static inline bool tu_fifo_empty(const tu_fifo_t *f) { // return number of items in fifo, capped to fifo's depth TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_count(const tu_fifo_t *f) { - return tu_min16(tu_ff_overflow_count(f->depth, f->wr_idx, f->rd_idx), f->depth); + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return tu_min16(tu_ff_overflow_count(f->depth, wr_idx, rd_idx), f->depth); } // check if fifo is full TU_ATTR_ALWAYS_INLINE static inline bool tu_fifo_full(const tu_fifo_t *f) { - return tu_ff_overflow_count(f->depth, f->wr_idx, f->rd_idx) >= f->depth; + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return tu_ff_overflow_count(f->depth, wr_idx, rd_idx) >= f->depth; } TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_remaining(const tu_fifo_t *f) { - return tu_ff_remaining_local(f->depth, f->wr_idx, f->rd_idx); + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return tu_ff_remaining_local(f->depth, wr_idx, rd_idx); } #ifdef __cplusplus diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h index 1a4e517b7..edc7ecc3e 100644 --- a/src/common/tusb_mcu.h +++ b/src/common/tusb_mcu.h @@ -71,13 +71,24 @@ #define TUP_DCD_ENDPOINT_MAX 5 #elif TU_CHECK_MCU(OPT_MCU_LPC54) + #include "fsl_device_registers.h" + // TODO USB0 has 5, USB1 has 6 #define TUP_USBIP_IP3511 + + #if !defined(LPC54114_cm4_SERIES) && !defined(LPC54114_cm0plus_SERIES) + #define TUP_USBIP_IP3516 + #define TUP_USBIP_OHCI + #define TUP_USBIP_OHCI_NXP + #define TUP_OHCI_RHPORTS 1 // 1 downstream port + #endif + #define TUP_DCD_ENDPOINT_MAX 6 #elif TU_CHECK_MCU(OPT_MCU_LPC55) // TODO USB0 has 5, USB1 has 6 #define TUP_USBIP_IP3511 + #define TUP_USBIP_IP3516 #define TUP_USBIP_OHCI #define TUP_USBIP_OHCI_NXP #define TUP_OHCI_RHPORTS 1 // 1 downstream port @@ -172,6 +183,10 @@ #define TUP_RHPORT_HIGHSPEED 1 #define TUD_ENDPOINT_ONE_DIRECTION_ONLY + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_SAMX7X_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + #elif TU_CHECK_MCU(OPT_MCU_PIC32MZ) #define TUP_DCD_ENDPOINT_MAX 8 #define TUD_ENDPOINT_ONE_DIRECTION_ONLY @@ -635,17 +650,7 @@ #define TUP_USBIP_DWC2_AT32 #define TUP_DCD_ENDPOINT_MAX 4 -#elif TU_CHECK_MCU(OPT_MCU_AT32F435_437) - #define TUP_USBIP_DWC2 - #define TUP_USBIP_DWC2_AT32 - #define TUP_DCD_ENDPOINT_MAX 8 - -#elif TU_CHECK_MCU(OPT_MCU_AT32F423) - #define TUP_USBIP_DWC2 - #define TUP_USBIP_DWC2_AT32 - #define TUP_DCD_ENDPOINT_MAX 8 - -#elif TU_CHECK_MCU(OPT_MCU_AT32F402_405) +#elif TU_CHECK_MCU(OPT_MCU_AT32F402_405, OPT_MCU_AT32F423, OPT_MCU_AT32F425, OPT_MCU_AT32F435_437, OPT_MCU_AT32F45X) #define TUP_USBIP_DWC2 #define TUP_USBIP_DWC2_AT32 #define TUP_DCD_ENDPOINT_MAX 8 @@ -657,11 +662,6 @@ #define TUP_RHPORT_HIGHSPEED 1 // Port0: FS, Port1: HS #endif -#elif TU_CHECK_MCU(OPT_MCU_AT32F425) - #define TUP_USBIP_DWC2 - #define TUP_USBIP_DWC2_AT32 - #define TUP_DCD_ENDPOINT_MAX 8 - //--------------------------------------------------------------------+ // HPMicro //--------------------------------------------------------------------+ diff --git a/src/common/tusb_private.h b/src/common/tusb_private.h index 10e12c2af..17ab267c3 100644 --- a/src/common/tusb_private.h +++ b/src/common/tusb_private.h @@ -24,8 +24,8 @@ * This file is part of the TinyUSB stack. */ -#ifndef TUSB_PRIVATE_H_ -#define TUSB_PRIVATE_H_ +#ifndef TUSB_PRIVATE_H +#define TUSB_PRIVATE_H // Internal Helper used by Host and Device Stack @@ -33,17 +33,11 @@ extern "C" { #endif -//--------------------------------------------------------------------+ -// Configuration -//--------------------------------------------------------------------+ +typedef void (*tusb_defer_func_t)(uintptr_t param); -#if CFG_TUD_ENABLED && CFG_TUD_VENDOR && (CFG_TUD_VENDOR_TX_BUFSIZE == 0 || CFG_TUD_VENDOR_RX_BUFSIZE == 0) - #define CFG_TUSB_EDPT_STREAM_NO_FIFO_ENABLED 1 -#endif - -#ifndef CFG_TUSB_EDPT_STREAM_NO_FIFO_ENABLED - #define CFG_TUSB_EDPT_STREAM_NO_FIFO_ENABLED 0 -#endif + //--------------------------------------------------------------------+ + // Configuration + //--------------------------------------------------------------------+ #define TUP_USBIP_CONTROLLER_NUM 2 extern tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM]; @@ -87,9 +81,8 @@ typedef struct { bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed); #else TU_ATTR_ALWAYS_INLINE static inline bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed) { - (void)desc_ep; (void)speed; - return true; + return tu_edpt_packet_size(desc_ep) > 0; } #endif diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index d473e53e6..8a48a0f04 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -341,12 +341,10 @@ typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< DEVICE Descriptor Type. uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). - uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF). uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF). uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF). uint8_t bMaxPacketSize0 ; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices is fixed to 64. - uint16_t idVendor ; ///< Vendor ID (assigned by the USB-IF). uint16_t idProduct ; ///< Product ID (assigned by the manufacturer). uint16_t bcdDevice ; ///< Device release number in binary-coded decimal. @@ -554,7 +552,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_number(uint8_t addr) { } TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) { - return (uint8_t) (num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0u)); + return (uint8_t) (num | (dir == (uint8_t)TUSB_DIR_IN ? (uint8_t)TUSB_DIR_IN_MASK : 0u)); } TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(tusb_desc_endpoint_t const* desc_ep) { diff --git a/src/common/tusb_verify.h b/src/common/tusb_verify.h index c9e06361c..bd00b9d11 100644 --- a/src/common/tusb_verify.h +++ b/src/common/tusb_verify.h @@ -73,8 +73,13 @@ #define TU_MESS_FAILED() do {} while (0) #endif +// Custom defined application function +#ifdef CFG_TUSB_DEBUG_BREAKPOINT + extern void CFG_TUSB_DEBUG_BREAKPOINT(void); + #define TU_BREAKPOINT() CFG_TUSB_DEBUG_BREAKPOINT() + // Halt CPU (breakpoint) when hitting error, only apply for Cortex M3, M4, M7, M33. M55 -#if defined(__ARM_ARCH_7M__) || defined (__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__) || defined(__ARM_ARCH_8_1M_MAIN__) || \ +#elif defined(__ARM_ARCH_7M__) || defined (__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__) || defined(__ARM_ARCH_8_1M_MAIN__) || \ defined(__ARM7M__) || defined (__ARM7EM__) || defined(__ARM8M_MAINLINE__) || defined(__ARM8EM_MAINLINE__) #define TU_BREAKPOINT() do { \ volatile uint32_t* ARM_CM_DHCSR = ((volatile uint32_t*) 0xE000EDF0UL); /* Cortex M CoreDebug->DHCSR */ \ diff --git a/src/device/usbd.c b/src/device/usbd.c index 1e21c667a..42903576c 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -334,6 +334,19 @@ static const usbd_class_driver_t _usbd_driver[] = { .sof = NULL }, #endif + + #if CFG_TUD_PRINTER + { + .name = DRIVER_NAME("PRINTER"), + .init = printerd_init, + .deinit = printerd_deinit, + .reset = printerd_reset, + .open = printerd_open, + .control_xfer_cb = printerd_control_xfer_cb, + .xfer_cb = printerd_xfer_cb, + .sof = NULL + }, + #endif }; enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(_usbd_driver) }; @@ -585,7 +598,7 @@ bool tud_deinit(uint8_t rhport) { // Deinit device controller driver dcd_int_disable(rhport); dcd_disconnect(rhport); - TU_VERIFY(dcd_deinit(rhport)); + TU_ASSERT(dcd_deinit(rhport)); // Deinit class drivers for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { @@ -666,8 +679,14 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) { return; } - // Loop until there is no more events in the queue - while (1) { + // Loop until there are no more events in the queue or CFG_TUD_TASK_EVENTS_PER_RUN is reached + for (unsigned epr = 0;; epr++) { +#if CFG_TUD_TASK_EVENTS_PER_RUN > 0 + if (epr >= CFG_TUD_TASK_EVENTS_PER_RUN) { + TU_LOG_USBD("USBD event limit (" TU_XSTRING(CFG_TUD_TASK_EVENTS_PER_RUN) ") reached\r\n"); + break; + } +#endif dcd_event_t event; if (!osal_queue_receive(_usbd_q, &event, timeout_ms)) { return; @@ -817,7 +836,9 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL if (TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type && p_request->bRequest <= TUSB_REQ_SYNCH_FRAME) { TU_LOG_USBD(" %s", tu_str_std_request[p_request->bRequest]); - if (TUSB_REQ_GET_DESCRIPTOR != p_request->bRequest) TU_LOG_USBD("\r\n"); + if (TUSB_REQ_GET_DESCRIPTOR != p_request->bRequest) { + TU_LOG_USBD("\r\n"); + } } #endif @@ -953,7 +974,25 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const //------------- Class/Interface Specific Request -------------// case TUSB_REQ_RCPT_INTERFACE: { - uint8_t const itf = tu_u16_low(p_request->wIndex); + uint8_t itf; + #if CFG_TUD_PRINTER + // Printer GET_DEVICE_ID has a weird wIndex = interface (high) | alt (low) + // attempt to interpret this as a printer request if matched + if (TUSB_REQ_TYPE_CLASS == p_request->bmRequestType_bit.type && + TUSB_DIR_IN == p_request->bmRequestType_bit.direction && + TUSB_PRINTER_REQUEST_GET_DEVICE_ID == p_request->bRequest) { + itf = tu_u16_high(p_request->wIndex); + if (itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)) { + const usbd_class_driver_t * driver = get_driver(_usbd_dev.itf2drv[itf]); + if (driver != NULL && driver->control_xfer_cb == printerd_control_xfer_cb) { + if (invoke_class_control(rhport, driver, p_request)) { + return true; + } + } + } + } + #endif + itf = tu_u16_low(p_request->wIndex); TU_VERIFY(itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)); usbd_class_driver_t const * driver = get_driver(_usbd_dev.itf2drv[itf]); diff --git a/src/device/usbd.h b/src/device/usbd.h index bd5a3c395..d3a6dccbb 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -41,8 +41,13 @@ enum { typedef struct { uint16_t bm_double_buffered; // bitmap of IN endpoints to be double buffered, only effective for bulk endpoints + bool vbus_sensing; // Vbus pin is used for device connection detection, mandatory for tud_umount_cb() } tud_configure_dwc2_t; + #ifndef CFG_TUD_CONFIGURE_DWC2_DEFAULT + #define CFG_TUD_CONFIGURE_DWC2_DEFAULT {.bm_double_buffered = 0, .vbus_sensing = CFG_TUD_VBUS_DETECT_HW} + #endif + typedef union { tud_configure_dwc2_t dwc2; } tud_configure_param_t; @@ -292,6 +297,19 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ /* Endpoint In */\ 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 +//--------------------------------------------------------------------+ +// Printer Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_PRINTER_DESC_LEN (9 + 7 + 7) // one interface, two endpoints + +#define TUD_PRINTER_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_PRINTER, 1, 2, _stridx,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 //--------------------------------------------------------------------+ // MTP Descriptor Templates @@ -819,7 +837,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ /* Interface */ \ 9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUD_DFU_APP_CLASS, TUD_DFU_APP_SUBCLASS, DFU_PROTOCOL_RT, _stridx, \ /* Function */ \ - 9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0101) + 9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0110) //--------------------------------------------------------------------+ // DFU Descriptor Templates @@ -833,7 +851,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ #define TUD_DFU_DESCRIPTOR(_itfnum, _alt_count, _stridx, _attr, _timeout, _xfer_size) \ TU_XSTRCAT(TUD_DFU_ALT_,_alt_count)(_itfnum, 0, _stridx), \ /* Function */ \ - 9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0101) + 9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0110) #define TUD_DFU_ALT(_itfnum, _alt, _stridx) \ /* Interface */ \ diff --git a/src/host/hcd.h b/src/host/hcd.h index 36a7f5da5..47d672f9e 100644 --- a/src/host/hcd.h +++ b/src/host/hcd.h @@ -59,7 +59,7 @@ typedef enum { HCD_EVENT_XFER_COMPLETE, USBH_EVENT_FUNC_CALL, // Not an HCD event - HCD_EVENT_COUNT + HCD_EVENT_INVALID } hcd_eventid_t; typedef struct { @@ -72,7 +72,6 @@ typedef struct { struct { uint8_t hub_addr; uint8_t hub_port; - uint8_t speed; } connection; // XFER_COMPLETE diff --git a/src/host/hub.c b/src/host/hub.c index 3baaff30f..7c6735ed4 100644 --- a/src/host/hub.c +++ b/src/host/hub.c @@ -48,7 +48,7 @@ typedef struct { uint8_t bNbrPorts; uint8_t bPwrOn2PwrGood_2ms; // port power on to good, in 2ms unit // uint16_t wHubCharacteristics; - + bool mtt; hub_port_status_response_t port_status; } hub_interface_t; @@ -218,23 +218,37 @@ bool hub_deinit(void) { return true; } -uint16_t hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { - (void) rhport; - - TU_VERIFY(TUSB_CLASS_HUB == itf_desc->bInterfaceClass && - 0 == itf_desc->bInterfaceSubClass, 0); - TU_VERIFY(itf_desc->bInterfaceProtocol <= 1, 0); // not support multiple TT yet +uint16_t hub_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + (void)rhport; + TU_VERIFY(TUSB_CLASS_HUB == itf_desc->bInterfaceClass && 0 == itf_desc->bInterfaceSubClass, 0); - const uint16_t drv_len = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t); + const uint16_t itf_ep_len = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t); + uint16_t drv_len = itf_ep_len; TU_ASSERT(drv_len <= max_len, 0); + hub_interface_t *p_hub = get_hub_itf(dev_addr); + const tusb_desc_interface_t *desc_itf_use = itf_desc; // interface to use for endpoint open + + // Check device descriptor for MTT hub (bDeviceProtocol == 2) + // MTT hub has 2 alt settings: alt 0 is STT (protocol 1), alt 1 is MTT (protocol 2) + // Consume both alt settings and use alt setting 1 for endpoint + tusb_desc_device_t desc_dev; + if (tuh_descriptor_get_device_local(dev_addr, &desc_dev) && desc_dev.bDeviceProtocol == HUB_PROTOCOL_HIGH_SPEED_MTT) { + drv_len += itf_ep_len; + TU_ASSERT(drv_len <= max_len, 0); + const tusb_desc_interface_t *desc_alt1 = (const tusb_desc_interface_t *)((const uint8_t *)itf_desc + itf_ep_len); + TU_ASSERT(desc_alt1->bDescriptorType == TUSB_DESC_INTERFACE && desc_alt1->bInterfaceClass == TUSB_CLASS_HUB && + desc_alt1->bInterfaceProtocol == HUB_PROTOCOL_HIGH_SPEED_MTT, + 0); + p_hub->mtt = true; + desc_itf_use = desc_alt1; + } + // Interrupt Status endpoint - tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); - TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && - TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)tu_desc_next(desc_itf_use); + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); TU_ASSERT(tuh_edpt_open(dev_addr, desc_ep), 0); - hub_interface_t* p_hub = get_hub_itf(dev_addr); p_hub->itf_num = itf_desc->bInterfaceNumber; p_hub->ep_in = desc_ep->bEndpointAddress; @@ -269,12 +283,27 @@ bool hub_edpt_status_xfer(uint8_t daddr) { //--------------------------------------------------------------------+ static void config_set_port_power (tuh_xfer_t* xfer); static void config_port_power_complete (tuh_xfer_t* xfer); +static void config_get_hub_descriptor(tuh_xfer_t* xfer); bool hub_set_config(uint8_t daddr, uint8_t itf_num) { hub_interface_t* p_hub = get_hub_itf(daddr); TU_ASSERT(itf_num == p_hub->itf_num); - hub_epbuf_t* p_epbuf = get_hub_epbuf(daddr); + if (p_hub->mtt) { + // Set Alternate Setting 1 for MTT hub + TU_ASSERT(tuh_interface_set(daddr, itf_num, 1, config_get_hub_descriptor, 0)); + } else { + tuh_xfer_t xfer; + xfer.daddr = daddr; + xfer.ep_addr = 0; + xfer.user_data = 0; + config_get_hub_descriptor(&xfer); + } + + return true; +} + +static void config_get_hub_descriptor(tuh_xfer_t* xfer) { // Get Hub Descriptor tusb_control_request_t const request = { .bmRequestType_bit = { @@ -288,17 +317,11 @@ bool hub_set_config(uint8_t daddr, uint8_t itf_num) { .wLength = sizeof(hub_desc_cs_t) }; - tuh_xfer_t xfer = { - .daddr = daddr, - .ep_addr = 0, - .setup = &request, - .buffer = p_epbuf->ctrl_buf, - .complete_cb = config_set_port_power, - .user_data = 0 - }; + xfer->setup = &request; + xfer->buffer = get_hub_epbuf(xfer->daddr)->ctrl_buf; + xfer->complete_cb = config_set_port_power; - TU_ASSERT(tuh_control_xfer(&xfer)); - return true; + TU_ASSERT(tuh_control_xfer(xfer), ); } static void config_set_port_power (tuh_xfer_t* xfer) { @@ -308,7 +331,7 @@ static void config_set_port_power (tuh_xfer_t* xfer) { hub_interface_t* p_hub = get_hub_itf(daddr); hub_epbuf_t* p_epbuf = get_hub_epbuf(daddr); - // only use number of ports in hub descriptor + // only use the number of ports in the hub descriptor hub_desc_cs_t const* desc_hub = (hub_desc_cs_t const*) p_epbuf->ctrl_buf; p_hub->bNbrPorts = desc_hub->bNbrPorts; p_hub->bPwrOn2PwrGood_2ms = desc_hub->bPwrOn2PwrGood; diff --git a/src/host/hub.h b/src/host/hub.h index d9750f8a5..8a7feda70 100644 --- a/src/host/hub.h +++ b/src/host/hub.h @@ -92,6 +92,13 @@ enum { HUB_CHARS_OVER_CURRENT_INDIVIDUAL = 1, }; +// Hub Interface Protocol (USB 2.0 spec Table 11-16) +typedef enum { + HUB_PROTOCOL_FULL_SPEED = 0, // Full speed hub + HUB_PROTOCOL_HIGH_SPEED_STT = 1, // Hi-speed hub with single TT + HUB_PROTOCOL_HIGH_SPEED_MTT = 2, // Hi-speed hub with multiple TTs +} hub_protocol_t; + typedef struct TU_ATTR_PACKED{ uint8_t bLength ; ///< Size of descriptor uint8_t bDescriptorType ; ///< Other_speed_Configuration Type diff --git a/src/host/usbh.c b/src/host/usbh.c index a725b7c8b..75df6bf60 100644 --- a/src/host/usbh.c +++ b/src/host/usbh.c @@ -104,10 +104,9 @@ TU_ATTR_WEAK void tuh_umount_cb(uint8_t daddr) { //--------------------------------------------------------------------+ // Data Structure //--------------------------------------------------------------------+ -typedef struct { - tuh_bus_info_t bus_info; - // Device Descriptor +// Device Descriptor (without bLength and bDescriptorType header) +typedef struct TU_ATTR_PACKED { uint16_t bcdUSB; uint8_t bDeviceClass; uint8_t bDeviceSubClass; @@ -120,6 +119,13 @@ typedef struct { uint8_t iProduct; uint8_t iSerialNumber; uint8_t bNumConfigurations; +} desc_device_noheader_t; + +TU_VERIFY_STATIC( sizeof(desc_device_noheader_t) == 16u, "size is not correct"); + +typedef struct { + tuh_bus_info_t bus_info; + desc_device_noheader_t desc_device; // Device State struct TU_ATTR_PACKED { @@ -169,6 +175,12 @@ static OSAL_SPINLOCK_DEF(_usbh_spin, usbh_int_set); OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); static osal_queue_t _usbh_q; + #if CFG_TUH_HUB +// Deferred attachment queue, only needed when using hub +OSAL_QUEUE_DEF(usbh_int_set, _usbh_daqdef, CFG_TUH_HUB, hcd_event_t); +static osal_queue_t _usbh_daq; + #endif + // Control transfers: since most controllers do not support multiple control transfers // on multiple devices concurrently and control transfers are not used much except for // enumeration, we will only execute control transfers one at a time. @@ -184,11 +196,18 @@ typedef struct { } usbh_ctrl_xfer_info_t; typedef struct { + tusb_defer_func_t func; + uintptr_t arg; + uint32_t at_ms; +} usbh_call_after_t; + +typedef struct { uint8_t controller_id; // controller ID uint8_t enumerating_daddr; // device address of the device being enumerated uint8_t attach_debouncing_bm; // bitmask for roothub port attach debouncing tuh_bus_info_t dev0_bus; // bus info for dev0 in enumeration usbh_ctrl_xfer_info_t ctrl_xfer_info; // control transfer + usbh_call_after_t call_after; } usbh_data_t; static usbh_data_t _usbh_data = { @@ -311,7 +330,8 @@ TU_ATTR_ALWAYS_INLINE static inline usbh_class_driver_t const *get_driver(uint8_ //--------------------------------------------------------------------+ // Function Inline and Prototypes //--------------------------------------------------------------------+ -static bool enum_new_device(hcd_event_t* event); +static void enum_new_device(hcd_event_t* event); +static void enum_delay_async(uintptr_t state); static void process_remove_event(hcd_event_t *event); static void remove_device_tree(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port); static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size); @@ -349,6 +369,15 @@ TU_ATTR_ALWAYS_INLINE static inline bool usbh_setup_send(uint8_t daddr, const ui return ret; } +bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t param) { + TU_ASSERT(_usbh_data.call_after.func == NULL); + TU_LOG_USBH("USBH schedule function after %u ms\r\n", (unsigned int)ms); + _usbh_data.call_after.func = func; + _usbh_data.call_after.arg = param; + _usbh_data.call_after.at_ms = tusb_time_millis_api() + ms; + return true; +} + TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8_t daddr) { hcd_device_close(rhport, daddr); @@ -360,6 +389,10 @@ TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8 // invalidate if enumerating if (daddr == _usbh_data.enumerating_daddr) { _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + // clear enum delay function of the device being removed + if (_usbh_data.call_after.func == enum_delay_async) { + _usbh_data.call_after.func = NULL; + } } } @@ -386,10 +419,10 @@ bool tuh_vid_pid_get(uint8_t dev_addr, uint16_t *vid, uint16_t *pid) { *vid = *pid = 0; usbh_device_t const *dev = get_device(dev_addr); - TU_VERIFY(dev && dev->addressed && dev->idVendor != 0); + TU_VERIFY(dev && dev->addressed && dev->desc_device.idVendor != 0); - *vid = dev->idVendor; - *pid = dev->idProduct; + *vid = dev->desc_device.idVendor; + *pid = dev->desc_device.idProduct; return true; } @@ -400,18 +433,7 @@ bool tuh_descriptor_get_device_local(uint8_t daddr, tusb_desc_device_t* desc_dev desc_device->bLength = sizeof(tusb_desc_device_t); desc_device->bDescriptorType = TUSB_DESC_DEVICE; - desc_device->bcdUSB = dev->bcdUSB; - desc_device->bDeviceClass = dev->bDeviceClass; - desc_device->bDeviceSubClass = dev->bDeviceSubClass; - desc_device->bDeviceProtocol = dev->bDeviceProtocol; - desc_device->bMaxPacketSize0 = dev->bMaxPacketSize0; - desc_device->idVendor = dev->idVendor; - desc_device->idProduct = dev->idProduct; - desc_device->bcdDevice = dev->bcdDevice; - desc_device->iManufacturer = dev->iManufacturer; - desc_device->iProduct = dev->iProduct; - desc_device->iSerialNumber = dev->iSerialNumber; - desc_device->bNumConfigurations = dev->bNumConfigurations; + memcpy((uint8_t*) desc_device + offsetof(tusb_desc_device_t, bcdUSB), &dev->desc_device, sizeof(desc_device_noheader_t)); return true; } @@ -493,11 +515,17 @@ bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { _usbh_q = osal_queue_create(&_usbh_qdef); TU_ASSERT(_usbh_q != NULL); -#if OSAL_MUTEX_REQUIRED + #if CFG_TUH_HUB + // Deferred attachment queue + _usbh_daq = osal_queue_create(&_usbh_daqdef); + TU_ASSERT(_usbh_daq != NULL); + #endif + + #if OSAL_MUTEX_REQUIRED // Init mutex _usbh_mutex = osal_mutex_create(&_usbh_mutexdef); TU_ASSERT(_usbh_mutex); -#endif + #endif // Get application driver if available _app_driver = usbh_app_driver_get_cb(&_app_driver_count); @@ -538,7 +566,7 @@ bool tuh_deinit(uint8_t rhport) { // deinit host controller hcd_int_disable(rhport); - hcd_deinit(rhport); + TU_ASSERT(hcd_deinit(rhport)); _usbh_data.controller_id = TUSB_INDEX_INVALID_8; // remove all devices on this rhport (hub_addr = 0, hub_port = 0) @@ -558,11 +586,16 @@ bool tuh_deinit(uint8_t rhport) { osal_queue_delete(_usbh_q); _usbh_q = NULL; - #if OSAL_MUTEX_REQUIRED + #if CFG_TUH_HUB + osal_queue_delete(_usbh_daq); + _usbh_daq = NULL; + #endif + + #if OSAL_MUTEX_REQUIRED // TODO make sure there is no task waiting on this mutex osal_mutex_delete(_usbh_mutex); _usbh_mutex = NULL; - #endif + #endif } return true; @@ -570,9 +603,19 @@ bool tuh_deinit(uint8_t rhport) { bool tuh_task_event_ready(void) { if (!tuh_inited()) { - return false; // Skip if stack is not initialized + return false; // Skip if tusb stack is not initialized } - return !osal_queue_empty(_usbh_q); + if (!osal_queue_empty(_usbh_q)) { + return true; + } + + #if CFG_TUH_HUB + if (!osal_queue_empty(_usbh_daq)) { + return true; + } + #endif + + return false; } /* USB Host Driver task @@ -592,45 +635,87 @@ bool tuh_task_event_ready(void) { @endcode */ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { - (void) in_isr; // not implemented yet - // Skip if stack is not initialized if (!tuh_inited()) { return; } - // Loop until there is no more events in the queue - while (1) { + (void) in_isr; // not implemented yet + + // Loop until there are no more events in the queue or CFG_TUH_TASK_EVENTS_PER_RUN is reached + for (unsigned epr = 0;; epr++) { + #if CFG_TUH_TASK_EVENTS_PER_RUN > 0 + if (epr >= CFG_TUH_TASK_EVENTS_PER_RUN) { + TU_LOG_USBH("USBH event limit (" TU_XSTRING(CFG_TUH_TASK_EVENTS_PER_RUN) ") reached\r\n"); + break; + } + #endif + + // Process call_after_ms function if ms is reached + tusb_defer_func_t after_cb = _usbh_data.call_after.func; + if (after_cb) { + int32_t remain_ms = (int32_t)(_usbh_data.call_after.at_ms - tusb_time_millis_api()); + if (remain_ms <= 0) { + // delay expired, run callback now + TU_LOG_USBH("USBH invoke scheduled function\r\n"); + _usbh_data.call_after.func = NULL; + after_cb(_usbh_data.call_after.arg); + } + + // above after_cb() can re-schedule another function, we need to re-check and reduce timeout of + // the main event timeout to make sure we aren't blocking more than call_after remaining ms. + if (_usbh_data.call_after.func != NULL) { + remain_ms = (int32_t) (_usbh_data.call_after.at_ms - tusb_time_millis_api()); + if (remain_ms <= 0) { + timeout_ms = 0; // expired already + } else if (timeout_ms > (uint32_t)remain_ms) { + timeout_ms = (uint32_t)remain_ms; + } + } + } + hcd_event_t event; - if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) { return; } + + #if CFG_TUH_HUB + // Get deferred device attachments if none is enumerating + bool has_deferred_attach = false; + if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) { + // zero wait to avoid blocking the main event queue + has_deferred_attach = osal_queue_receive(_usbh_daq, &event, 0); + } + + if (!has_deferred_attach) // skip event queue to process deferred attach + #endif + { + if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) { + return; + } + } switch (event.event_id) { case HCD_EVENT_DEVICE_ATTACH: // Should we miss the hub detach event due to high traffic, Or due to physical debouncing, some devices can - // cause multiple attaches (actually reset) without detach event. + // cause multiple attaches (actually reset) without a detached event. // Force remove currently mounted with the same bus info (rhport, hub addr, hub port) if exists process_remove_event(&event); // due to the shared control buffer, we must fully complete enumerating one device first. - // TODO better to have an separated queue for newly attached devices if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) { // New device attached and we are ready TU_LOG_USBH("[%u:] USBH Device Attach\r\n", event.rhport); _usbh_data.enumerating_daddr = 0; // enumerate new device with address 0 enum_new_device(&event); - } else { - // currently enumerating another device + } + #if CFG_TUH_HUB + else { TU_LOG_USBH("[%u:] USBH Defer Attach until current enumeration complete\r\n", event.rhport); - const bool is_empty = osal_queue_empty(_usbh_q); - queue_event(&event, in_isr); - if (is_empty) { - return; // Exit if this is the only event in the queue, otherwise we loop forever - } + TU_ASSERT(osal_queue_send(_usbh_daq, &event, in_isr), ); } + #endif break; case HCD_EVENT_DEVICE_REMOVE: - TU_LOG_USBH("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port); + TU_LOG_USBH("[%u:%u:%u] USBH Device Removed\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port); process_remove_event(&event); break; @@ -703,10 +788,8 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { break; } -#if CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO - // return if there is no more events, for application to run other background - if (osal_queue_empty(_usbh_q)) return; -#endif + // allow to exit tuh_task() if there is no event in the next run + timeout_ms = 0; } } @@ -764,10 +847,8 @@ bool tuh_control_xfer (tuh_xfer_t* xfer) { while (result == XFER_RESULT_INVALID) { // Note: this can be called within an callback ie. part of tuh_task() - // therefore event with RTOS tuh_task() still need to be invoked - if (tuh_task_event_ready()) { - tuh_task(); - } + // therefore even with RTOS tuh_task_ext() still need to be invoked + tuh_task_ext(0, false); // TODO probably some timeout to prevent hanged } @@ -1064,7 +1145,7 @@ static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size) { } bool tuh_edpt_open(uint8_t dev_addr, tusb_desc_endpoint_t const* desc_ep) { - // HACK: some device incorrectly always report 512 bulk regardless of link speed, overwrite descriptor to force 64 + // HACK: some device incorrectly always reports 512 bulk regardless of link speed, overwrite descriptor to force 64 if (desc_ep->bmAttributes.xfer == TUSB_XFER_BULK && tu_edpt_packet_size(desc_ep) > 64 && tuh_speed_get(dev_addr) == TUSB_SPEED_FULL) { TU_LOG1(" WARN: EP max packet size is 512 in fullspeed, force to 64\r\n"); @@ -1189,24 +1270,24 @@ bool tuh_descriptor_get_manufacturer_string(uint8_t daddr, uint16_t language_id, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { usbh_device_t const* dev = get_device(daddr); - TU_VERIFY(dev && dev->iManufacturer); - return tuh_descriptor_get_string(daddr, dev->iManufacturer, language_id, buffer, len, complete_cb, user_data); + TU_VERIFY(dev && dev->desc_device.iManufacturer); + return tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, language_id, buffer, len, complete_cb, user_data); } // Get product string descriptor bool tuh_descriptor_get_product_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { usbh_device_t const* dev = get_device(daddr); - TU_VERIFY(dev && dev->iProduct); - return tuh_descriptor_get_string(daddr, dev->iProduct, language_id, buffer, len, complete_cb, user_data); + TU_VERIFY(dev && dev->desc_device.iProduct); + return tuh_descriptor_get_string(daddr, dev->desc_device.iProduct, language_id, buffer, len, complete_cb, user_data); } // Get serial string descriptor bool tuh_descriptor_get_serial_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { usbh_device_t const* dev = get_device(daddr); - TU_VERIFY(dev && dev->iSerialNumber); - return tuh_descriptor_get_string(daddr, dev->iSerialNumber, language_id, buffer, len, complete_cb, user_data); + TU_VERIFY(dev && dev->desc_device.iSerialNumber); + return tuh_descriptor_get_string(daddr, dev->desc_device.iSerialNumber, language_id, buffer, len, complete_cb, user_data); } // Get HID report descriptor @@ -1417,10 +1498,10 @@ enum { // USB 2.0 specs 7.1.7 for timing enum { ENUM_IDLE, ENUM_HUB_RERSET, - ENUM_HUB_GET_STATUS_AFTER_RESET, + ENUM_HUB_RESET_COMPLETE, ENUM_HUB_CLEAR_RESET, + ENUM_HUB_CLEAR_RESET_RETRY, // 2nd attempt waiting for hub reset ENUM_HUB_CLEAR_RESET_COMPLETE, - ENUM_ADDR0_DEVICE_DESC, ENUM_SET_ADDR, ENUM_GET_DEVICE_DESC, @@ -1439,138 +1520,175 @@ enum { }; static uint8_t enum_get_new_address(bool is_hub); -static bool enum_parse_configuration_desc (uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg); -static void enum_full_complete(bool success); -static void process_enumeration(tuh_xfer_t* xfer); +static bool enum_parse_configuration_desc(uint8_t dev_addr, const tusb_desc_configuration_t *desc_cfg); +static void enum_full_complete(bool success); +static void process_enumeration(tuh_xfer_t *xfer); -// start a new enumeration process -static bool enum_new_device(hcd_event_t* event) { - tuh_bus_info_t* dev0_bus = &_usbh_data.dev0_bus; - dev0_bus->rhport = event->rhport; - dev0_bus->hub_addr = event->connection.hub_addr; - dev0_bus->hub_port = event->connection.hub_port; +enum { + ENUM_AFTER_DEBOUNCING_DELAY, + ENUM_AFTER_RESET_ROOT_DELAY, + ENUM_AFTER_RESET_ROOT_POST_DELAY, + ENUM_AFTER_RESET_HUB_DELAY, + ENUM_AFTER_RESET_HUB_DELAY_RETRY, + ENUM_AFTER_RESET_RECOVERY_DELAY, + ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY, +}; - // wait until device connection is stable TODO non blocking - tusb_time_delay_ms_api(ENUM_DEBOUNCING_DELAY_MS); +// process async delay in enumeration +static void enum_delay_async(uintptr_t state) { + tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus; + switch (state) { + case ENUM_AFTER_DEBOUNCING_DELAY: + #if CFG_TUH_HUB + if (dev0_bus->hub_addr != 0) { + // connected via hub + TU_VERIFY(dev0_bus->hub_port != 0, ); + TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, + ENUM_HUB_RERSET), ); + } else + #endif + { + // connected directly to roothub + _usbh_data.attach_debouncing_bm &= (uint8_t)~TU_BIT(dev0_bus->rhport); // clear roothub debouncing delay + if (!hcd_port_connect_status(dev0_bus->rhport)) { + TU_LOG_USBH("Device unplugged while debouncing\r\n"); + enum_full_complete(false); + return; + } + hcd_port_reset(dev0_bus->rhport); // reset port + usbh_defer_func_ms_async(ENUM_RESET_ROOT_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_ROOT_DELAY); + } + break; + + case ENUM_AFTER_RESET_ROOT_DELAY: + hcd_port_reset_end(dev0_bus->rhport); + usbh_defer_func_ms_async(ENUM_RESET_ROOT_POST_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_ROOT_POST_DELAY); + break; + + case ENUM_AFTER_RESET_ROOT_POST_DELAY: + if (!hcd_port_connect_status(dev0_bus->rhport)) { + // device unplugged while delaying + enum_full_complete(false); + return; + } - if (dev0_bus->hub_addr == 0) { - // connected directly to roothub - // USB bus not active and frame number is not available yet. - // need to depend on tusb_time_millis_api() TODO non blocking + dev0_bus->speed = hcd_port_speed_get(dev0_bus->rhport); + TU_LOG_USBH("%s Speed\r\n", tu_str_speed[dev0_bus->speed]); - _usbh_data.attach_debouncing_bm &= (uint8_t) ~TU_BIT(dev0_bus->rhport); // clear roothub debouncing delay + // fake transfer to kick-off the enumeration process + tuh_xfer_t xfer; + xfer.daddr = 0; + xfer.result = XFER_RESULT_SUCCESS; + xfer.user_data = ENUM_ADDR0_DEVICE_DESC; + process_enumeration(&xfer); + break; - if (!hcd_port_connect_status(dev0_bus->rhport)) { - TU_LOG_USBH("Device unplugged while debouncing\r\n"); - enum_full_complete(false); - return true; - } + #if CFG_TUH_HUB + case ENUM_AFTER_RESET_HUB_DELAY: + case ENUM_AFTER_RESET_HUB_DELAY_RETRY: + // get status after reset complete to check for reset change + TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, + state == ENUM_AFTER_RESET_HUB_DELAY ? ENUM_HUB_CLEAR_RESET + : ENUM_HUB_CLEAR_RESET_RETRY), ); + break; + #endif - // reset device - hcd_port_reset(dev0_bus->rhport); - tusb_time_delay_ms_api(ENUM_RESET_ROOT_DELAY_MS); - hcd_port_reset_end(dev0_bus->rhport); - tusb_time_delay_ms_api(ENUM_RESET_ROOT_POST_DELAY_MS); + case ENUM_AFTER_RESET_RECOVERY_DELAY: + // TODO probably doesn't need to open/close each enumeration + if (!usbh_edpt_control_open(0, 8)) { + TU_LOG_USBH("Failed to open dev0's control endpoint\r\n"); + enum_full_complete(false); // Stop enumeration gracefully + return; + } + // Get first 8 bytes of device descriptor for control endpoint size + TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n"); + TU_ASSERT(tuh_descriptor_get_device(0, _usbh_epbuf.ctrl, 8, process_enumeration, ENUM_SET_ADDR), ); + break; - if (!hcd_port_connect_status(dev0_bus->rhport)) { - // device unplugged while delaying - enum_full_complete(false); - return true; + case ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY: { + const uint8_t new_addr = _usbh_data.enumerating_daddr; + usbh_device_t *new_dev = get_device(new_addr); + TU_ASSERT(new_dev, ); + if (!usbh_edpt_control_open(new_addr, new_dev->desc_device.bMaxPacketSize0)) { + TU_LOG_USBH("Failed to open new device's control endpoint\r\n"); + clear_device(new_dev); + enum_full_complete(false); + return; + } + TU_LOG_USBH("Get Device Descriptor\r\n"); + TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t), process_enumeration, + ENUM_GET_STRING_LANGUAGE_ID_LEN), ); + break; } - dev0_bus->speed = hcd_port_speed_get(dev0_bus->rhport); - TU_LOG_USBH("%s Speed\r\n", tu_str_speed[dev0_bus->speed]); - - // fake transfer to kick-off the enumeration process - tuh_xfer_t xfer; - xfer.daddr = 0; - xfer.result = XFER_RESULT_SUCCESS; - xfer.user_data = ENUM_ADDR0_DEVICE_DESC; - process_enumeration(&xfer); - } - #if CFG_TUH_HUB - else { - // connected via hub - TU_VERIFY(dev0_bus->hub_port != 0); - TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, - process_enumeration, ENUM_HUB_RERSET)); + default: + break; } - #endif // hub +} - return true; +// start a new enumeration process +static void enum_new_device(hcd_event_t *event) { + tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus; + dev0_bus->rhport = event->rhport; + dev0_bus->hub_addr = event->connection.hub_addr; + dev0_bus->hub_port = event->connection.hub_port; + usbh_defer_func_ms_async(ENUM_DEBOUNCING_DELAY_MS, enum_delay_async, ENUM_AFTER_DEBOUNCING_DELAY); } // process device enumeration -static void process_enumeration(tuh_xfer_t* xfer) { - // Retry a few times while enumerating since device can be unstable when starting up - static uint8_t failed_count = 0; +static void process_enumeration(tuh_xfer_t *xfer) { if (XFER_RESULT_FAILED == xfer->result) { - enum { - ATTEMPT_COUNT_MAX = 3, - ATTEMPT_DELAY_MS = 100 - }; - - // retry if not reaching max attempt - failed_count++; - bool retry = (_usbh_data.enumerating_daddr != TUSB_INDEX_INVALID_8) && (failed_count < ATTEMPT_COUNT_MAX); - if (retry) { - tusb_time_delay_ms_api(ATTEMPT_DELAY_MS); // delay a bit - TU_LOG_USBH("Enumeration attempt %u/%u\r\n", failed_count+1, ATTEMPT_COUNT_MAX); - retry = tuh_control_xfer(xfer); - } - - if (!retry) { - enum_full_complete(false); // complete as failed - } + enum_full_complete(false); // failed to enum return; } - failed_count = 0; - uint8_t const daddr = xfer->daddr; - uintptr_t const state = xfer->user_data; - usbh_device_t* dev = get_device(daddr); - tuh_bus_info_t* dev0_bus = &_usbh_data.dev0_bus; + const uint8_t daddr = xfer->daddr; + const uintptr_t state = xfer->user_data; + usbh_device_t *dev = get_device(daddr); + tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus; if (daddr > 0) { TU_ASSERT(dev != NULL,); } uint16_t langid = 0x0409; // default is English + bool is_enum_failed = false; switch (state) { - #if CFG_TUH_HUB + #if CFG_TUH_HUB case ENUM_HUB_RERSET: { hub_port_status_response_t port_status; hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); if (0 == port_status.status.connection) { TU_LOG_USBH("Device unplugged from hub while debouncing\r\n"); - enum_full_complete(false); - return; + is_enum_failed = true; + } else { + TU_ASSERT(hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, + ENUM_HUB_RESET_COMPLETE), ); } - - TU_ASSERT(hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, ENUM_HUB_GET_STATUS_AFTER_RESET),); break; } - case ENUM_HUB_GET_STATUS_AFTER_RESET: { - tusb_time_delay_ms_api(ENUM_RESET_HUB_DELAY_MS); // wait for reset to take effect - - // get status to check for reset change - TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, ENUM_HUB_CLEAR_RESET),); + case ENUM_HUB_RESET_COMPLETE: + // wait for reset to take effect + usbh_defer_func_ms_async(ENUM_RESET_HUB_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_HUB_DELAY); break; - } - case ENUM_HUB_CLEAR_RESET: { + case ENUM_HUB_CLEAR_RESET: + case ENUM_HUB_CLEAR_RESET_RETRY: { hub_port_status_response_t port_status; hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); if (1 == port_status.change.reset) { // Acknowledge Port Reset Change - TU_ASSERT(hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, ENUM_HUB_CLEAR_RESET_COMPLETE),); + TU_ASSERT(hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, + ENUM_HUB_CLEAR_RESET_COMPLETE), ); + } else if (state == ENUM_HUB_CLEAR_RESET) { + // retry one more time if reset change not set yet + usbh_defer_func_ms_async(ENUM_RESET_HUB_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_HUB_DELAY_RETRY); } else { - // maybe retry if reset change not set but we need timeout to prevent infinite loop - // TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, ENUM_HUB_CLEAR_RESET_COMPLETE),); + // retry but still not set --> failed + is_enum_failed = true; } - break; } @@ -1580,70 +1698,50 @@ static void process_enumeration(tuh_xfer_t* xfer) { if (0 == port_status.status.connection) { TU_LOG_USBH("Device unplugged from hub (not addressed yet)\r\n"); - enum_full_complete(false); - return; + is_enum_failed = true; + break; } - dev0_bus->speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH : - (port_status.status.low_speed) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; - + dev0_bus->speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH + : (port_status.status.low_speed) ? TUSB_SPEED_LOW + : TUSB_SPEED_FULL; TU_ATTR_FALLTHROUGH; } - #endif - - case ENUM_ADDR0_DEVICE_DESC: { - tusb_time_delay_ms_api(ENUM_RESET_RECOVERY_DELAY_MS); // reset recovery - - // TODO probably doesn't need to open/close each enumeration - uint8_t const addr0 = 0; - if (!usbh_edpt_control_open(addr0, 8)) { - // Stop enumeration gracefully - enum_full_complete(false); - TU_ASSERT(false,); - } + #endif - // Get first 8 bytes of device descriptor for control endpoint size - TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n"); - TU_ASSERT(tuh_descriptor_get_device(addr0, _usbh_epbuf.ctrl, 8, - process_enumeration, ENUM_SET_ADDR),); + case ENUM_ADDR0_DEVICE_DESC: + usbh_defer_func_ms_async(ENUM_RESET_RECOVERY_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_RECOVERY_DELAY); break; - } case ENUM_SET_ADDR: { const tusb_desc_device_t *desc_device = (const tusb_desc_device_t *) _usbh_epbuf.ctrl; + if (!(desc_device->bDescriptorType == TUSB_DESC_DEVICE && desc_device->bMaxPacketSize0 >= 8)) { + TU_LOG_USBH("Invalid Device descriptor\r\n"); + is_enum_failed = true; + break; + } + const uint8_t new_addr = enum_get_new_address(desc_device->bDeviceClass == TUSB_CLASS_HUB); TU_ASSERT(new_addr != 0,); usbh_device_t* new_dev = get_device(new_addr); new_dev->bus_info = *dev0_bus; new_dev->connected = 1; - new_dev->bMaxPacketSize0 = desc_device->bMaxPacketSize0; + new_dev->desc_device.bMaxPacketSize0 = desc_device->bMaxPacketSize0; - TU_ASSERT(tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC),); + TU_ASSERT(tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC), ); break; } case ENUM_GET_DEVICE_DESC: { - tusb_time_delay_ms_api(ENUM_SET_ADDRESS_RECOVERY_DELAY_MS); // set address recovery - - const uint8_t new_addr = (uint8_t) tu_le16toh(xfer->setup->wValue); - usbh_device_t* new_dev = get_device(new_addr); - TU_ASSERT(new_dev,); - new_dev->addressed = 1; + const uint8_t new_addr = (uint8_t)tu_le16toh(xfer->setup->wValue); + usbh_device_t *new_dev = get_device(new_addr); + TU_ASSERT(new_dev, ); + new_dev->addressed = 1; _usbh_data.enumerating_daddr = new_addr; usbh_device_close(dev0_bus->rhport, 0); // close dev0 - - if (!usbh_edpt_control_open(new_addr, new_dev->bMaxPacketSize0)) { // open new control endpoint - // Stop enumeration gracefully - clear_device(new_dev); - enum_full_complete(false); - TU_ASSERT(false,); - } - - TU_LOG_USBH("Get Device Descriptor\r\n"); - TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t), - process_enumeration, ENUM_GET_STRING_LANGUAGE_ID_LEN),); + usbh_defer_func_ms_async(ENUM_SET_ADDRESS_RECOVERY_DELAY_MS, enum_delay_async, ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY); break; } @@ -1653,18 +1751,7 @@ static void process_enumeration(tuh_xfer_t* xfer) { // save the received device descriptor tusb_desc_device_t const *desc_device = (tusb_desc_device_t const *) _usbh_epbuf.ctrl; - dev->bcdUSB = desc_device->bcdUSB; - dev->bDeviceClass = desc_device->bDeviceClass; - dev->bDeviceSubClass = desc_device->bDeviceSubClass; - dev->bDeviceProtocol = desc_device->bDeviceProtocol; - dev->bMaxPacketSize0 = desc_device->bMaxPacketSize0; - dev->idVendor = desc_device->idVendor; - dev->idProduct = desc_device->idProduct; - dev->bcdDevice = desc_device->bcdDevice; - dev->iManufacturer = desc_device->iManufacturer; - dev->iProduct = desc_device->iProduct; - dev->iSerialNumber = desc_device->iSerialNumber; - dev->bNumConfigurations = desc_device->bNumConfigurations; + memcpy(&dev->desc_device, (const uint8_t*) desc_device + offsetof(tusb_desc_device_t, bcdUSB), sizeof(desc_device_noheader_t)); tuh_enum_descriptor_device_cb(daddr, desc_device); // callback tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, 2, @@ -1684,8 +1771,8 @@ static void process_enumeration(tuh_xfer_t* xfer) { if (desc_langid->bLength >= 4) { langid = tu_le16toh(desc_langid->utf16le[0]); // previous request is langid } - if (dev->iManufacturer != 0) { - tuh_descriptor_get_string(daddr, dev->iManufacturer, langid, _usbh_epbuf.ctrl, 2, + if (dev->desc_device.iManufacturer != 0) { + tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_MANUFACTURER); break; } @@ -1693,10 +1780,10 @@ static void process_enumeration(tuh_xfer_t* xfer) { } case ENUM_GET_STRING_MANUFACTURER: { - if (dev->iManufacturer != 0) { + if (dev->desc_device.iManufacturer != 0) { langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request const uint8_t str_len = xfer->buffer[0]; - tuh_descriptor_get_string(daddr, dev->iManufacturer, langid, _usbh_epbuf.ctrl, str_len, + tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl, str_len, process_enumeration, ENUM_GET_STRING_PRODUCT_LEN); break; } @@ -1704,22 +1791,22 @@ static void process_enumeration(tuh_xfer_t* xfer) { } case ENUM_GET_STRING_PRODUCT_LEN: { - if (dev->iProduct != 0) { + if (dev->desc_device.iProduct != 0) { if (state == ENUM_GET_STRING_PRODUCT_LEN) { langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through } tuh_descriptor_get_string( - daddr, dev->iProduct, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_PRODUCT); + daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_PRODUCT); break; } TU_ATTR_FALLTHROUGH; } case ENUM_GET_STRING_PRODUCT: { - if (dev->iProduct != 0) { + if (dev->desc_device.iProduct != 0) { langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request const uint8_t str_len = xfer->buffer[0]; - tuh_descriptor_get_string(daddr, dev->iProduct, langid, _usbh_epbuf.ctrl, str_len, + tuh_descriptor_get_string(daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, str_len, process_enumeration, ENUM_GET_STRING_SERIAL_LEN); break; } @@ -1727,22 +1814,22 @@ static void process_enumeration(tuh_xfer_t* xfer) { } case ENUM_GET_STRING_SERIAL_LEN: { - if (dev->iSerialNumber != 0) { + if (dev->desc_device.iSerialNumber != 0) { if (state == ENUM_GET_STRING_SERIAL_LEN) { langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through } tuh_descriptor_get_string( - daddr, dev->iSerialNumber, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_SERIAL); + daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_SERIAL); break; } TU_ATTR_FALLTHROUGH; } case ENUM_GET_STRING_SERIAL: { - if (dev->iSerialNumber != 0) { + if (dev->desc_device.iSerialNumber != 0) { langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request const uint8_t str_len = xfer->buffer[0]; - tuh_descriptor_get_string(daddr, dev->iSerialNumber, langid, _usbh_epbuf.ctrl, str_len, + tuh_descriptor_get_string(daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl, str_len, process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC); break; } @@ -1781,7 +1868,7 @@ static void process_enumeration(tuh_xfer_t* xfer) { TU_ASSERT(tuh_configuration_set(daddr, config_idx+1u, process_enumeration, ENUM_CONFIG_DRIVER),); } else { config_idx++; - TU_ASSERT(config_idx < dev->bNumConfigurations,); + TU_ASSERT(config_idx < dev->desc_device.bNumConfigurations,); TU_LOG_USBH("Get Configuration[%u] Descriptor (9 bytes)\r\n", config_idx); TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9, process_enumeration, ENUM_GET_FULL_CONFIG_DESC),); @@ -1793,11 +1880,12 @@ static void process_enumeration(tuh_xfer_t* xfer) { TU_LOG_USBH("Device configured\r\n"); dev->configured = 1; - #if CFG_TUH_HUB + #if CFG_TUH_HUB + // get next hub status now since device can be unplugged before set_configure() is complete if (_usbh_data.dev0_bus.hub_addr != 0) { - hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); // get next hub status + hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); } - #endif + #endif // Parse configuration & set up drivers // driver_open() must not make any usb transfer @@ -1812,9 +1900,13 @@ static void process_enumeration(tuh_xfer_t* xfer) { } default: - enum_full_complete(false); // stop enumeration if unknown state + is_enum_failed = true; break; } + + if (is_enum_failed) { + enum_full_complete(false); + } } static uint8_t enum_get_new_address(bool is_hub) { @@ -1835,6 +1927,12 @@ static uint8_t enum_get_new_address(bool is_hub) { } } +#if CFG_TUH_HUB + if ( is_hub ) { + TU_LOG1("All addresses are occupied, try to increase CFG_TUH_HUB value.\r\n"); + } +#endif // CFG_TUH_HUB + return 0; // invalid address } @@ -1846,10 +1944,10 @@ static bool enum_parse_configuration_desc(uint8_t dev_addr, tusb_desc_configurat TU_LOG_USBH("Parsing Configuration descriptor (wTotalLength = %u)\r\n", total_len); - // parse each interfaces + // parse all interfaces while (tu_desc_in_bounds(p_desc, desc_end)) { if (0 == tu_desc_len(p_desc)) { - // A zero length descriptor indicates that the device is off spec (e.g. wrong wTotalLength). + // A zero-length descriptor indicates that the device is off spec (e.g. wrong wTotalLength). // Parsed interfaces should still be usable TU_LOG_USBH("Encountered a zero-length descriptor after %" PRIu32 " bytes\r\n", (uint32_t)p_desc - (uint32_t)desc_cfg); break; @@ -1865,7 +1963,7 @@ static bool enum_parse_configuration_desc(uint8_t dev_addr, tusb_desc_configurat // uint16_t const drv_len = tu_desc_get_interface_total_len(desc_itf, assoc_itf_count, (uint16_t) // (desc_end-p_desc)); TU_ASSERT(drv_len >= sizeof(tusb_desc_interface_t)); - // Find driver for this interface + // Find a driver for this interface const uint16_t remaining_len = (uint16_t)(desc_end - p_desc); uint8_t drv_id; for (drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { @@ -1913,7 +2011,7 @@ void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) { } } - // all interface are configured + // all interfaces are configured if (itf_num == CFG_TUH_INTERFACE_MAX) { enum_full_complete(true); @@ -1928,16 +2026,17 @@ void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) { static void enum_full_complete(bool success) { (void)success; - // mark enumeration as complete - _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + TU_LOG_USBH("Enumeration complete: success = %u\r\n", success); -#if CFG_TUH_HUB + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; // mark enumeration as complete + _usbh_data.call_after.func = NULL; + + #if CFG_TUH_HUB // Hub status is already requested in case of successful enumeration - if (_usbh_data.dev0_bus.hub_addr != 0 && !success) { - hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); // get next hub status + if (!success && _usbh_data.dev0_bus.hub_addr != 0) { + hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); } -#endif - + #endif } #endif diff --git a/src/host/usbh.h b/src/host/usbh.h index d86efbcb2..143d36f8c 100644 --- a/src/host/usbh.h +++ b/src/host/usbh.h @@ -96,6 +96,7 @@ enum { TUH_CFGID_RPI_PIO_USB_CONFIGURATION = 100, // cfg_param: pio_usb_configuration_t TUH_CFGID_MAX3421 = 200, TUH_CFGID_FSDEV = 300, + TUH_CFGID_DWC2 = 400 }; typedef struct { @@ -108,10 +109,15 @@ typedef struct { uint8_t max_nak; // max NAK per endpoint per frame to save CPU usage (0=unlimited) } tuh_configure_fsdev_t; +typedef struct { + bool use_hs_phy; // Always use high-speed ULPI/UTMI phy even when working at full-speed +} tuh_configure_dwc2_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_dwc2_t dwc2; } tuh_configure_param_t; //--------------------------------------------------------------------+ diff --git a/src/host/usbh_pvt.h b/src/host/usbh_pvt.h index 57428e3c5..adb6a8c44 100644 --- a/src/host/usbh_pvt.h +++ b/src/host/usbh_pvt.h @@ -68,8 +68,12 @@ uint8_t* usbh_get_enum_buf(void); void usbh_int_set(bool enabled); +// Invoke this function later in tuh_task() by putting it into task queue void usbh_defer_func(osal_task_func_t func, void *param, bool in_isr); +// Schedules a function to be called after certain time asynchronously +bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t param); + void usbh_spin_lock(bool in_isr); void usbh_spin_unlock(bool in_isr); diff --git a/src/osal/osal.h b/src/osal/osal.h index 44521620f..4840463f3 100644 --- a/src/osal/osal.h +++ b/src/osal/osal.h @@ -65,6 +65,8 @@ typedef void (*osal_task_func_t)(void* param); #include "osal_rtx4.h" #elif CFG_TUSB_OS == OPT_OS_ZEPHYR #include "osal_zephyr.h" +#elif CFG_TUSB_OS == OPT_OS_THREADX + #include "osal_threadx.h" #elif CFG_TUSB_OS == OPT_OS_CUSTOM #include "tusb_os_custom.h" // implemented by application #else @@ -74,6 +76,8 @@ typedef void (*osal_task_func_t)(void* param); /*-------------------------------------------------------------------- OSAL Porting API Should be implemented as static inline function in osal_port.h header + uint32_t osal_time_millis(void); + void osal_spin_init(osal_spinlock_t *ctx); void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr); diff --git a/src/osal/osal_freertos.h b/src/osal/osal_freertos.h index 9aeda4d01..db724179d 100644 --- a/src/osal/osal_freertos.h +++ b/src/osal/osal_freertos.h @@ -99,6 +99,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { vTaskDelay(pdMS_TO_TICKS(msec)); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return pdTICKS_TO_MS(xTaskGetTickCount()); +} + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ @@ -137,11 +141,12 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { if (in_isr) { - if (TUP_MCU_MULTIPLE_CORE == 0) { - (void) ctx; - return; // single core MCU does not need to lock in ISR - } + #if TUP_MCU_MULTIPLE_CORE *ctx = taskENTER_CRITICAL_FROM_ISR(); + #else + (void) ctx; + return; // single core MCU does not need to lock in ISR + #endif } else { taskENTER_CRITICAL(); } @@ -149,11 +154,12 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bo TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { if (in_isr) { - if (TUP_MCU_MULTIPLE_CORE == 0) { - (void) ctx; - return; // single core MCU does not need to lock in ISR - } + #if TUP_MCU_MULTIPLE_CORE taskEXIT_CRITICAL_FROM_ISR(*ctx); + #else + (void) ctx; + return; // single core MCU does not need to lock in ISR + #endif } else { taskEXIT_CRITICAL(); } diff --git a/src/osal/osal_mynewt.h b/src/osal/osal_mynewt.h index 6d51f8ec3..94124ca81 100644 --- a/src/osal/osal_mynewt.h +++ b/src/osal/osal_mynewt.h @@ -40,6 +40,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { os_time_delay( os_time_ms_to_ticks32(msec) ); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return os_time_ticks_to_ms32(os_time_get()); +} + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ diff --git a/src/osal/osal_none.h b/src/osal/osal_none.h index 6ab18ace8..7bf6029d6 100644 --- a/src/osal/osal_none.h +++ b/src/osal/osal_none.h @@ -31,6 +31,8 @@ extern "C" { #endif +// osal_time_millis() is not provided, tusb_time_millis_api() must be implemented by user application + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ @@ -184,7 +186,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, v (void) msec; // not used, always behave as msec = 0 qhdl->interrupt_set(false); - const bool success = tu_fifo_read_n(&qhdl->ff, data, qhdl->item_size); + const bool success = (tu_fifo_read_n(&qhdl->ff, data, qhdl->item_size) > 0); qhdl->interrupt_set(true); return success; @@ -195,7 +197,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void qhdl->interrupt_set(false); } - const bool success = tu_fifo_write_n(&qhdl->ff, data, qhdl->item_size); + const bool success = (tu_fifo_write_n(&qhdl->ff, data, qhdl->item_size) > 0); if (!in_isr) { qhdl->interrupt_set(true); diff --git a/src/osal/osal_pico.h b/src/osal/osal_pico.h index 79b728e9a..6a0a21bb3 100644 --- a/src/osal/osal_pico.h +++ b/src/osal/osal_pico.h @@ -43,6 +43,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { sleep_ms(msec); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return to_ms_since_boot(get_absolute_time()); +} + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ diff --git a/src/osal/osal_rtthread.h b/src/osal/osal_rtthread.h index a778f5425..f560281c5 100644 --- a/src/osal/osal_rtthread.h +++ b/src/osal/osal_rtthread.h @@ -42,6 +42,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { rt_thread_mdelay(msec); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return (uint32_t)((((uint64_t)rt_tick_get()) * 1000) / RT_TICK_PER_SECOND); +} + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ diff --git a/src/osal/osal_rtx4.h b/src/osal/osal_rtx4.h index 35860ddd5..e1930c96c 100644 --- a/src/osal/osal_rtx4.h +++ b/src/osal/osal_rtx4.h @@ -46,6 +46,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { os_dly_wait(lo); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return os_time_get(); +} + TU_ATTR_ALWAYS_INLINE static inline uint16_t msec2wait(uint32_t msec) { if (msec == OSAL_TIMEOUT_WAIT_FOREVER) { return 0xFFFF; diff --git a/src/osal/osal_threadx.h b/src/osal/osal_threadx.h new file mode 100644 index 000000000..6bcf9c5ab --- /dev/null +++ b/src/osal/osal_threadx.h @@ -0,0 +1,201 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_OSAL_THREADX_H_ +#define TUSB_OSAL_THREADX_H_ + +// ThreadX Headers +#include "tx_api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) { + if ( msec == TX_WAIT_FOREVER ) { + return TX_WAIT_FOREVER; + } + if ( msec == 0 ) { + return 0; + } + + uint32_t ticks = msec * TX_TIMER_TICKS_PER_SECOND / 1000; + + // TX_TIMER_TICKS_PER_SECOND is less than 1000 and 1 tick > 1 ms + // we still need to delay at least 1 tick + if ( ticks == 0 ) { + ticks = 1; + } + + return ticks; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return (uint32_t)((uint64_t) tx_time_get() * 1000u / TX_TIMER_TICKS_PER_SECOND); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + tx_thread_sleep(_osal_ms2tick(msec)); +} + +//--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +//--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +typedef struct { + void (* interrupt_set)(bool); +} osal_spinlock_t; + +// For SMP, spinlock must be locked by hardware, cannot just use interrupt +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name = { .interrupt_set = _int_set } + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + (void) ctx; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + if (!in_isr) { + ctx->interrupt_set(false); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!in_isr) { + ctx->interrupt_set(true); + } +} + + +//--------------------------------------------------------------------+ +// Binary Semaphore API (act) +//--------------------------------------------------------------------+ +// Note: semaphores are not used in tinyusb for now, and their API has not been tested + +typedef TX_SEMAPHORE osal_semaphore_def_t, * osal_semaphore_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef) { + tx_semaphore_create(semdef, TX_NULL, 0); + return semdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t sem_hdl) { + (void) sem_hdl; + return TX_SUCCESS == tx_semaphore_delete(sem_hdl); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { + (void) in_isr; + return TX_SUCCESS == tx_semaphore_put(sem_hdl); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { + return TX_SUCCESS == tx_semaphore_get(sem_hdl, _osal_ms2tick(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) { + (void) sem_hdl; +} + +//--------------------------------------------------------------------+ +// MUTEX API +//--------------------------------------------------------------------+ +typedef TX_MUTEX osal_mutex_def_t, *osal_mutex_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) { + if (TX_SUCCESS == tx_mutex_create(mdef, mdef->tx_mutex_name, TX_NO_INHERIT)) { + return mdef; + } else { + return NULL; + } +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + (void) mutex_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { + return TX_SUCCESS == tx_mutex_get(mutex_hdl, _osal_ms2tick(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { + return TX_SUCCESS == tx_mutex_put(mutex_hdl); +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ + +typedef TX_QUEUE osal_queue_def_t, * osal_queue_t; + +// _int_set is not used with an RTOS _usbd_qdef + +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ +static _type _name##_buf[_depth]; \ +osal_queue_def_t _name = { \ + .tx_queue_name = (CHAR*)(uintptr_t)#_name, \ + .tx_queue_message_size = (sizeof(_type) + 3) / 4, \ + .tx_queue_capacity = _depth, \ + .tx_queue_start = (ULONG *) _name##_buf } + + +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { + return TX_SUCCESS == + tx_queue_create(qdef, qdef->tx_queue_name, qdef->tx_queue_message_size, qdef->tx_queue_start, qdef->tx_queue_capacity * qdef->tx_queue_message_size * 4) + ? qdef : 0; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + (void) qhdl; + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { + return 0 == tx_queue_receive(qhdl, data, _osal_ms2tick(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr) { + return 0 == tx_queue_send(qhdl, (VOID *)(uintptr_t) data, in_isr ? TX_NO_WAIT : TX_WAIT_FOREVER); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { + ULONG enqueued; + tx_queue_info_get(qhdl, 0, &enqueued, 0, 0, 0, 0); + return enqueued == 0; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/osal/osal_zephyr.h b/src/osal/osal_zephyr.h index 91f225f79..900ac786c 100644 --- a/src/osal/osal_zephyr.h +++ b/src/osal/osal_zephyr.h @@ -35,6 +35,10 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { k_msleep(msec); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return k_uptime_get_32(); +} + //--------------------------------------------------------------------+ // Spinlock API //--------------------------------------------------------------------+ diff --git a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h index 178eec419..c22aea887 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h +++ b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h @@ -47,10 +47,10 @@ static const ci_hs_controller_t _ci_controller[] = #define CI_HS_REG(_port) ((ci_hs_regs_t*) _ci_controller[_port].reg_base) -#define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ (_ci_controller[_p].irqnum) -#define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ(_ci_controller[_p].irqnum) +#define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) +#define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) -#define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ (_ci_controller[_p].irqnum) -#define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ(_ci_controller[_p].irqnum) +#define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) +#define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) #endif diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c index 2bba32ada..9ed75ffd9 100644 --- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c @@ -285,6 +285,23 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { return true; } +bool dcd_deinit(uint8_t rhport) { + ci_hs_regs_t* dcd_reg = CI_HS_REG(rhport); + + // disable all interrupt + dcd_reg->USBINTR = 0; + + // unattach from bus + dcd_reg->USBCMD &= ~USBCMD_RUN_STOP; + + // flush all endpoints + while (dcd_reg->ENDPTPRIME) {} + dcd_reg->ENDPTFLUSH = 0xFFFFFFFF; + while (dcd_reg->ENDPTFLUSH) {} + + return true; +} + void dcd_int_enable(uint8_t rhport) { CI_DCD_INT_ENABLE(rhport); } @@ -371,7 +388,7 @@ TU_ATTR_ALWAYS_INLINE static inline void ep_ctrl_mask(volatile uint32_t *epctrl, uint32_t or_mask) { uint32_t value = *epctrl; if (and_mask != 0) { - value &= (dir == TUSB_DIR_OUT) ? and_mask : (and_mask << 16u); + value &= (dir == TUSB_DIR_OUT) ? (and_mask | 0xFFFF0000u) : ((and_mask << 16u) | 0x0000FFFFu); } if (or_mask != 0) { value |= (dir == TUSB_DIR_OUT) ? or_mask : (or_mask << 16u); diff --git a/src/portable/chipidea/ci_hs/hcd_ci_hs.c b/src/portable/chipidea/ci_hs/hcd_ci_hs.c index 29ce0cd7f..c0d14fe57 100644 --- a/src/portable/chipidea/ci_hs/hcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/hcd_ci_hs.c @@ -93,21 +93,25 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { hcd_reg->USBCMD |= USBCMD_RESET; while (hcd_reg->USBCMD & USBCMD_RESET) {} - // Set mode to device, must be set immediately after reset + // Set mode to host, must be set immediately after reset #if CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX // LPC18XX/43XX need to set VBUS Power Select to HIGH - // RHPORT1 is fullspeed only (need external PHY for Highspeed) hcd_reg->USBMODE = USBMODE_CM_HOST | USBMODE_VBUS_POWER_SELECT; - if (rhport == 1) { - hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED; - } #else hcd_reg->USBMODE = USBMODE_CM_HOST; #endif + #if !TUH_OPT_HIGH_SPEED + hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED; + #endif + return ehci_init(rhport, (uint32_t)&hcd_reg->CAPLENGTH, (uint32_t)&hcd_reg->USBCMD); } +bool hcd_deinit(uint8_t rhport) { + return ehci_deinit(rhport); +} + void hcd_int_enable(uint8_t rhport) { CI_HCD_INT_ENABLE(rhport); } diff --git a/src/portable/ehci/ehci.c b/src/portable/ehci/ehci.c index 9b2cf98be..03c3b91fd 100644 --- a/src/portable/ehci/ehci.c +++ b/src/portable/ehci/ehci.c @@ -411,17 +411,22 @@ bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg) return true; } -#if 0 -static void ehci_stop(uint8_t rhport) { +bool ehci_deinit(uint8_t rhport) { (void) rhport; ehci_registers_t* regs = ehci_data.regs; + + // Disable all the interrupt + regs->inten = 0; + + // Disable schedules regs->command_bm.run_stop = 0; // USB Spec: controller has to stop within 16 uframe = 2 frames while( regs->status_bm.hc_halted == 0 ) {} + + return true; } -#endif //--------------------------------------------------------------------+ // Endpoint API diff --git a/src/portable/ehci/ehci_api.h b/src/portable/ehci/ehci_api.h index 79fbe702a..e9018639f 100644 --- a/src/portable/ehci/ehci_api.h +++ b/src/portable/ehci/ehci_api.h @@ -38,6 +38,9 @@ // Initialize EHCI driver bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg); +// De-initialize EHCI driver +bool ehci_deinit(uint8_t rhport); + #ifdef __cplusplus } #endif diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 3827be318..d329285e9 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -32,12 +32,6 @@ #define MUSB_DEBUG 2 #define MUSB_REGS(rhport) ((musb_regs_t*) MUSB_BASES[rhport]) -#if __GNUC__ > 8 && defined(__ARM_FEATURE_UNALIGNED) -/* GCC warns that an address may be unaligned, even though - * the target CPU has the capability for unaligned memory access. */ -_Pragma("GCC diagnostic ignored \"-Waddress-of-packed-member\""); -#endif - #include "musb_type.h" #include "device/dcd.h" @@ -73,7 +67,10 @@ typedef struct TU_ATTR_PACKED typedef struct { - tusb_control_request_t setup_packet; + union { + tusb_control_request_t setup_packet; + uint32_t setup_buffer[2]; + }; uint16_t remaining_ctrl; /* The number of bytes remaining in data stage of control transfer. */ int8_t status_out; pipe_state_t pipe0; @@ -174,10 +171,8 @@ static void process_setup_packet(uint8_t rhport) { musb_regs_t* musb_regs = MUSB_REGS(rhport); // Read setup packet - uint32_t *p = (void*)&_dcd.setup_packet; - volatile uint32_t *fifo_ptr = &musb_regs->fifo[0]; - p[0] = *fifo_ptr; - p[1] = *fifo_ptr; + _dcd.setup_buffer[0] = musb_regs->fifo[0]; + _dcd.setup_buffer[1] = musb_regs->fifo[0]; _dcd.pipe0.buf = NULL; _dcd.pipe0.length = 0; @@ -194,14 +189,13 @@ static void process_setup_packet(uint8_t rhport) { } } -static bool handle_xfer_in(uint8_t rhport, uint_fast8_t ep_addr) -{ +static bool handle_xfer_in(uint8_t rhport, uint_fast8_t ep_addr) { unsigned epnum = tu_edpt_number(ep_addr); unsigned epnum_minus1 = epnum - 1; pipe_state_t *pipe = &_dcd.pipe[tu_edpt_dir(ep_addr)][epnum_minus1]; const unsigned rem = pipe->remaining; - if (!rem) { + if (rem == 0 && pipe->length > 0) { pipe->buf = NULL; return true; } @@ -218,7 +212,7 @@ static bool handle_xfer_in(uint8_t rhport, uint_fast8_t ep_addr) tu_hwfifo_write_from_fifo(fifo_ptr, (tu_fifo_t *)buf, len, NULL); } else { tu_hwfifo_write(fifo_ptr, buf, len, NULL); - pipe->buf = buf + len; + pipe->buf = (uint8_t*)buf + len; } pipe->remaining = rem - len; } @@ -249,7 +243,7 @@ static bool handle_xfer_out(uint8_t rhport, uint_fast8_t ep_addr) tu_hwfifo_read_to_fifo(fifo_ptr, (tu_fifo_t *)buf, len, NULL); } else { tu_hwfifo_read(fifo_ptr, buf, len, NULL); - pipe->buf = buf + len; + pipe->buf = (uint8_t*)buf + len; } pipe->remaining = rem - len; } diff --git a/src/portable/mentor/musb/musb_ti.h b/src/portable/mentor/musb/musb_ti.h index d17e836ee..68e89d77d 100644 --- a/src/portable/mentor/musb/musb_ti.h +++ b/src/portable/mentor/musb/musb_ti.h @@ -35,7 +35,10 @@ #include "TM4C123.h" #define FIFO0_WORD FIFO0 #define FIFO1_WORD FIFO1 -//#elif CFG_TUSB_MCU == OPT_MCU_TM4C129 +#elif CFG_TUSB_MCU == OPT_MCU_TM4C129 + #include "TM4C129.h" + #define FIFO0_WORD FIFOA + #define FIFO1_WORD FIFOB #elif CFG_TUSB_MCU == OPT_MCU_MSP432E4 #include "msp.h" #else diff --git a/src/portable/mentor/musb/musb_type.h b/src/portable/mentor/musb/musb_type.h index 4e448c0ed..b2f6492fa 100644 --- a/src/portable/mentor/musb/musb_type.h +++ b/src/portable/mentor/musb/musb_type.h @@ -147,7 +147,7 @@ typedef struct TU_ATTR_PACKED { TU_VERIFY_STATIC(sizeof(musb_ep_csr_t) == 16, "size is not correct"); -typedef struct TU_ATTR_PACKED { +typedef struct { //------------- Common -------------// __IO uint8_t faddr; // 0x00: FADDR union { diff --git a/src/portable/microchip/samx7x/dcd_samx7x.c b/src/portable/microchip/samx7x/dcd_samx7x.c index b0a053c01..6da1e9778 100644 --- a/src/portable/microchip/samx7x/dcd_samx7x.c +++ b/src/portable/microchip/samx7x/dcd_samx7x.c @@ -1,133 +1,117 @@ /* -* The MIT License (MIT) -* -* Copyright (c) 2018, hathach (tinyusb.org) -* Copyright (c) 2021, HiFiPhile -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in -* all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -* THE SOFTWARE. -* -* This file is part of the TinyUSB stack. -*/ + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach (tinyusb.org) + * Copyright (c) 2021, HiFiPhile + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ #include "tusb_option.h" #if CFG_TUD_ENABLED && CFG_TUSB_MCU == OPT_MCU_SAMX7X -#include "device/dcd.h" -#include "sam.h" -#include "common_usb_regs.h" -//--------------------------------------------------------------------+ -// MACRO TYPEDEF CONSTANT ENUM DECLARATION -//--------------------------------------------------------------------+ - -// Since TinyUSB doesn't use SOF for now, and this interrupt too often (1ms interval) -// We disable SOF for now until needed later on -#ifndef USE_SOF -# define USE_SOF 0 -#endif - -// Dual bank can improve performance, but need 2 times bigger packet buffer -// As SAM7x has only 4KB packet buffer, use with caution ! -// Enable in FS mode as packets are smaller -#ifndef USE_DUAL_BANK -# if TUD_OPT_HIGH_SPEED -# define USE_DUAL_BANK 0 -# else -# define USE_DUAL_BANK 1 -# endif -#endif + #include "device/dcd.h" + #include "sam.h" + #include "samx7x_common.h" + //--------------------------------------------------------------------+ + // MACRO TYPEDEF CONSTANT ENUM DECLARATION + //--------------------------------------------------------------------+ -#define EP_GET_FIFO_PTR(ep, scale) (((TU_XSTRCAT(TU_STRCAT(uint, scale),_t) (*)[0x8000 / ((scale) / 8)])FIFO_RAM_ADDR)[(ep)]) + // Dual bank can improve performance, but need 2 times bigger packet buffer + // As SAM7x has only 4KB packet buffer, use with caution ! + // Enable in FS mode as packets are smaller + #ifndef USE_DUAL_BANK + #if TUD_OPT_HIGH_SPEED + #define USE_DUAL_BANK 0 + #else + #define USE_DUAL_BANK 1 + #endif + #endif -// DMA Channel Transfer Descriptor -typedef struct { - volatile uint32_t next_desc; - volatile uint32_t buff_addr; - volatile uint32_t chnl_ctrl; - uint32_t padding; -} dma_desc_t; + #define EP_GET_FIFO_PTR(ep, scale) \ + (((TU_XSTRCAT(TU_STRCAT(uint, scale), _t)(*)[0x8000 / ((scale) / 8)]) FIFO_RAM_ADDR)[(ep)]) // Transfer control context typedef struct { - uint8_t * buffer; - uint16_t total_len; - uint16_t queued_len; - uint16_t max_packet_size; - uint8_t interval; - tu_fifo_t * fifo; + uint8_t *buffer; + uint16_t total_len; + uint16_t queued_len; + uint16_t max_packet_size; + uint8_t interval; + tu_fifo_t *fifo; } xfer_ctl_t; static tusb_speed_t get_speed(void); -static void dcd_transmit_packet(xfer_ctl_t * xfer, uint8_t ep_ix); - -// DMA descriptors shouldn't be placed in ITCM ! -CFG_TUD_MEM_SECTION static dma_desc_t dma_desc[6]; +static void dcd_transmit_packet(xfer_ctl_t *xfer, uint8_t ep_ix); static xfer_ctl_t xfer_status[EP_MAX]; -static const tusb_desc_endpoint_t ep0_desc = -{ +static const tusb_desc_endpoint_t ep0_desc = { .bEndpointAddress = 0x00, .wMaxPacketSize = CFG_TUD_ENDPOINT0_SIZE, }; -TU_ATTR_ALWAYS_INLINE static inline void CleanInValidateCache(uint32_t *addr, int32_t size) -{ - if (SCB->CCR & SCB_CCR_DC_Msk) - { - SCB_CleanInvalidateDCache_by_Addr(addr, size); - } - else - { - __DSB(); - __ISB(); - } + #if CFG_TUD_MEM_DCACHE_ENABLE +bool dcd_dcache_clean(const void *addr, uint32_t data_size) { + TU_VERIFY(addr && data_size); + return samx7x_dcache_clean(addr, data_size); } + +bool dcd_dcache_invalidate(const void *addr, uint32_t data_size) { + TU_VERIFY(addr && data_size); + return samx7x_dcache_invalidate(addr, data_size); +} + +bool dcd_dcache_clean_invalidate(const void *addr, uint32_t data_size) { + TU_VERIFY(addr && data_size); + return samx7x_dcache_clean_invalidate(addr, data_size); +} + #endif //------------------------------------------------------------------ // Device API //------------------------------------------------------------------ // Initialize controller to device mode -bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rh_init; +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rh_init; dcd_connect(rhport); return true; } // Enable device interrupt -void dcd_int_enable (uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ((IRQn_Type) ID_USBHS); +void dcd_int_enable(uint8_t rhport) { + (void)rhport; + NVIC_EnableIRQ((IRQn_Type)ID_USBHS); } // Disable device interrupt -void dcd_int_disable (uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ((IRQn_Type) ID_USBHS); +void dcd_int_disable(uint8_t rhport) { + (void)rhport; + NVIC_DisableIRQ((IRQn_Type)ID_USBHS); } // Receive Set Address request, mcu port must also include status IN response -void dcd_set_address (uint8_t rhport, uint8_t dev_addr) -{ - (void) dev_addr; +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + (void)dev_addr; // DCD can only set address after status for this request is complete // do it at dcd_edpt0_status_complete() @@ -136,30 +120,26 @@ void dcd_set_address (uint8_t rhport, uint8_t dev_addr) } // Wake up host -void dcd_remote_wakeup (uint8_t rhport) -{ - (void) rhport; +void dcd_remote_wakeup(uint8_t rhport) { + (void)rhport; USB_REG->DEVCTRL |= DEVCTRL_RMWKUP; } // Connect by enabling internal pull-up resistor on D+/D- -void dcd_connect(uint8_t rhport) -{ - (void) rhport; +void dcd_connect(uint8_t rhport) { + (void)rhport; dcd_int_disable(rhport); // Enable the USB controller in device mode USB_REG->CTRL = CTRL_UIMOD | CTRL_USBE; - while (!(USB_REG->SR & SR_CLKUSABLE)); -#if TUD_OPT_HIGH_SPEED + while (!(USB_REG->SR & SR_CLKUSABLE)) + ; + #if TUD_OPT_HIGH_SPEED USB_REG->DEVCTRL &= ~DEVCTRL_SPDCONF; -#else + #else USB_REG->DEVCTRL |= DEVCTRL_SPDCONF_LOW_POWER; -#endif + #endif // Enable the End Of Reset, Suspend & Wakeup interrupts USB_REG->DEVIER = (DEVIER_EORSTES | DEVIER_SUSPES | DEVIER_WAKEUPES); -#if USE_SOF - USB_REG->DEVIER = DEVIER_SOFES; -#endif // Clear the End Of Reset, SOF & Wakeup interrupts USB_REG->DEVICR = (DEVICR_EORSTC | DEVICR_SOFC | DEVICR_WAKEUPC); // Manually set the Suspend Interrupt @@ -173,15 +153,15 @@ void dcd_connect(uint8_t rhport) } // Disconnect by disabling internal pull-up resistor on D+/D- -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; +void dcd_disconnect(uint8_t rhport) { + (void)rhport; dcd_int_disable(rhport); // Disable all endpoints USB_REG->DEVEPT &= ~(0x3FF << DEVEPT_EPEN0_Pos); // Unfreeze USB clock USB_REG->CTRL &= ~CTRL_FRZCLK; - while (!(USB_REG->SR & SR_CLKUSABLE)); + while (!(USB_REG->SR & SR_CLKUSABLE)) + ; // Clear all the pending interrupts USB_REG->DEVICR = DEVICR_Msk; // Disable all interrupts @@ -189,127 +169,106 @@ void dcd_disconnect(uint8_t rhport) // Detach the device USB_REG->DEVCTRL |= DEVCTRL_DETACH; // Disable the device address - USB_REG->DEVCTRL &=~(DEVCTRL_ADDEN | DEVCTRL_UADD); + USB_REG->DEVCTRL &= ~(DEVCTRL_ADDEN | DEVCTRL_UADD); } -void dcd_sof_enable(uint8_t rhport, bool en) -{ - (void) rhport; - (void) en; - - // TODO implement later +void dcd_sof_enable(uint8_t rhport, bool en) { + (void)rhport; + if (en) { + USB_REG->DEVIER = DEVIER_SOFES; + } else { + USB_REG->DEVIDR = DEVIDR_SOFEC; + } } -static tusb_speed_t get_speed(void) -{ +static tusb_speed_t get_speed(void) { switch (USB_REG->SR & SR_SPEED) { - case SR_SPEED_FULL_SPEED: - default: - return TUSB_SPEED_FULL; - case SR_SPEED_HIGH_SPEED: - return TUSB_SPEED_HIGH; - case SR_SPEED_LOW_SPEED: - return TUSB_SPEED_LOW; + case SR_SPEED_FULL_SPEED: + default: + return TUSB_SPEED_FULL; + case SR_SPEED_HIGH_SPEED: + return TUSB_SPEED_HIGH; + case SR_SPEED_LOW_SPEED: + return TUSB_SPEED_LOW; } } -static void dcd_ep_handler(uint8_t ep_ix) -{ +static void dcd_ep_handler(uint8_t ep_ix) { uint32_t int_status = USB_REG->DEVEPTISR[ep_ix]; int_status &= USB_REG->DEVEPTIMR[ep_ix]; - uint16_t count = (USB_REG->DEVEPTISR[ep_ix] & - DEVEPTISR_BYCT) >> DEVEPTISR_BYCT_Pos; - xfer_ctl_t *xfer = &xfer_status[ep_ix]; + uint16_t count = (USB_REG->DEVEPTISR[ep_ix] & DEVEPTISR_BYCT) >> DEVEPTISR_BYCT_Pos; + xfer_ctl_t *xfer = &xfer_status[ep_ix]; - if (ep_ix == 0U) - { + if (ep_ix == 0U) { static uint8_t ctrl_dir; - if (int_status & DEVEPTISR_CTRL_RXSTPI) - { + if (int_status & DEVEPTISR_CTRL_RXSTPI) { ctrl_dir = (USB_REG->DEVEPTISR[0] & DEVEPTISR_CTRL_CTRLDIR) >> DEVEPTISR_CTRL_CTRLDIR_Pos; // Setup packet should always be 8 bytes. If not, ignore it, and try again. - if (count == 8) - { - uint8_t *ptr = EP_GET_FIFO_PTR(0,8); + if (count == 8) { + uint8_t *ptr = EP_GET_FIFO_PTR(0, 8); dcd_event_setup_received(0, ptr, true); } // Ack and disable SETUP interrupt USB_REG->DEVEPTICR[0] = DEVEPTICR_CTRL_RXSTPIC; USB_REG->DEVEPTIDR[0] = DEVEPTIDR_CTRL_RXSTPEC; } - if (int_status & DEVEPTISR_RXOUTI) - { - uint8_t *ptr = EP_GET_FIFO_PTR(0,8); + if (int_status & DEVEPTISR_RXOUTI) { + uint8_t *ptr = EP_GET_FIFO_PTR(0, 8); - if (count && xfer->total_len) - { + if (count && xfer->total_len) { uint16_t remain = xfer->total_len - xfer->queued_len; - if (count > remain) - { + if (count > remain) { count = remain; } - if (xfer->buffer) - { + if (xfer->buffer) { memcpy(xfer->buffer + xfer->queued_len, ptr, count); - } else - { - tu_fifo_write_n(xfer->fifo, ptr, count); + } else { + tu_hwfifo_read_to_fifo(ptr, xfer->fifo, count, NULL); } xfer->queued_len = (uint16_t)(xfer->queued_len + count); } // Acknowledge the interrupt USB_REG->DEVEPTICR[0] = DEVEPTICR_RXOUTIC; - if ((count < xfer->max_packet_size) || (xfer->queued_len == xfer->total_len)) - { + if ((count < xfer->max_packet_size) || (xfer->queued_len == xfer->total_len)) { // RX COMPLETE dcd_event_xfer_complete(0, 0, xfer->queued_len, XFER_RESULT_SUCCESS, true); // Disable the interrupt USB_REG->DEVEPTIDR[0] = DEVEPTIDR_RXOUTEC; // Re-enable SETUP interrupt - if (ctrl_dir == 1) - { + if (ctrl_dir == 1) { USB_REG->DEVEPTIER[0] = DEVEPTIER_CTRL_RXSTPES; } } } - if (int_status & DEVEPTISR_TXINI) - { + if (int_status & DEVEPTISR_TXINI) { // Disable the interrupt USB_REG->DEVEPTIDR[0] = DEVEPTIDR_TXINEC; - if ((xfer->total_len != xfer->queued_len)) - { + if ((xfer->total_len != xfer->queued_len)) { // TX not complete dcd_transmit_packet(xfer, 0); - } else - { + } else { // TX complete dcd_event_xfer_complete(0, 0x80 + 0, xfer->total_len, XFER_RESULT_SUCCESS, true); // Re-enable SETUP interrupt - if (ctrl_dir == 0) - { + if (ctrl_dir == 0) { USB_REG->DEVEPTIER[0] = DEVEPTIER_CTRL_RXSTPES; } } } - } else - { - if (int_status & DEVEPTISR_RXOUTI) - { - if (count && xfer->total_len) - { + } else { + if (int_status & DEVEPTISR_RXOUTI) { + if (count && xfer->total_len) { uint16_t remain = xfer->total_len - xfer->queued_len; - if (count > remain) - { + if (count > remain) { count = remain; } - uint8_t *ptr = EP_GET_FIFO_PTR(ep_ix,8); - if (xfer->buffer) - { + uint8_t *ptr = EP_GET_FIFO_PTR(ep_ix, 8); + if (xfer->buffer) { memcpy(xfer->buffer + xfer->queued_len, ptr, count); } else { - tu_fifo_write_n(xfer->fifo, ptr, count); + tu_hwfifo_read_to_fifo(ptr, xfer->fifo, count, NULL); } xfer->queued_len = (uint16_t)(xfer->queued_len + count); } @@ -317,8 +276,7 @@ static void dcd_ep_handler(uint8_t ep_ix) USB_REG->DEVEPTIDR[ep_ix] = DEVEPTIDR_FIFOCONC; // Acknowledge the interrupt USB_REG->DEVEPTICR[ep_ix] = DEVEPTICR_RXOUTIC; - if ((count < xfer->max_packet_size) || (xfer->queued_len == xfer->total_len)) - { + if ((count < xfer->max_packet_size) || (xfer->queued_len == xfer->total_len)) { // RX COMPLETE dcd_event_xfer_complete(0, ep_ix, xfer->queued_len, XFER_RESULT_SUCCESS, true); // Disable the interrupt @@ -326,16 +284,13 @@ static void dcd_ep_handler(uint8_t ep_ix) // Though the host could still send, we don't know. } } - if (int_status & DEVEPTISR_TXINI) - { + if (int_status & DEVEPTISR_TXINI) { // Acknowledge the interrupt USB_REG->DEVEPTICR[ep_ix] = DEVEPTICR_TXINIC; - if ((xfer->total_len != xfer->queued_len)) - { + if ((xfer->total_len != xfer->queued_len)) { // TX not complete dcd_transmit_packet(xfer, ep_ix); - } else - { + } else { // TX complete dcd_event_xfer_complete(0, 0x80 + ep_ix, xfer->total_len, XFER_RESULT_SUCCESS, true); // Disable the interrupt @@ -345,45 +300,40 @@ static void dcd_ep_handler(uint8_t ep_ix) } } -static void dcd_dma_handler(uint8_t ep_ix) -{ +static void dcd_dma_handler(uint8_t ep_ix) { uint32_t status = USB_REG->DEVDMA[ep_ix - 1].DEVDMASTATUS; - if (status & DEVDMASTATUS_CHANN_ENB) - { + if (status & DEVDMASTATUS_CHANN_ENB) { return; // Ignore EOT_STA interrupt } // Disable DMA interrupt USB_REG->DEVIDR = DEVIDR_DMA_1 << (ep_ix - 1); - xfer_ctl_t *xfer = &xfer_status[ep_ix]; - uint16_t count = xfer->total_len - ((status & DEVDMASTATUS_BUFF_COUNT) >> DEVDMASTATUS_BUFF_COUNT_Pos); - if(USB_REG->DEVEPTCFG[ep_ix] & DEVEPTCFG_EPDIR) - { + xfer_ctl_t *xfer = &xfer_status[ep_ix]; + uint16_t count = xfer->total_len - ((status & DEVDMASTATUS_BUFF_COUNT) >> DEVDMASTATUS_BUFF_COUNT_Pos); + if (USB_REG->DEVEPTCFG[ep_ix] & DEVEPTCFG_EPDIR) { dcd_event_xfer_complete(0, 0x80 + ep_ix, count, XFER_RESULT_SUCCESS, true); - } else - { + } else { + dcd_dcache_invalidate(xfer->buffer, count); dcd_event_xfer_complete(0, ep_ix, count, XFER_RESULT_SUCCESS, true); } } -void dcd_int_handler(uint8_t rhport) -{ - (void) rhport; +void dcd_int_handler(uint8_t rhport) { + (void)rhport; uint32_t int_status = USB_REG->DEVISR; int_status &= USB_REG->DEVIMR; // End of reset interrupt - if (int_status & DEVISR_EORST) - { + if (int_status & DEVISR_EORST) { // Unfreeze USB clock USB_REG->CTRL &= ~CTRL_FRZCLK; - while(!(USB_REG->SR & SR_CLKUSABLE)); + while (!(USB_REG->SR & SR_CLKUSABLE)) + ; // Reset all endpoints - for (int ep_ix = 1; ep_ix < EP_MAX; ep_ix++) - { + for (int ep_ix = 1; ep_ix < EP_MAX; ep_ix++) { USB_REG->DEVEPT |= 1 << (DEVEPT_EPRST0_Pos + ep_ix); - USB_REG->DEVEPT &=~(1 << (DEVEPT_EPRST0_Pos + ep_ix)); + USB_REG->DEVEPT &= ~(1 << (DEVEPT_EPRST0_Pos + ep_ix)); } - dcd_edpt_open (0, &ep0_desc); + dcd_edpt_open(0, &ep0_desc); USB_REG->DEVICR = DEVICR_EORSTC; USB_REG->DEVICR = DEVICR_WAKEUPC; USB_REG->DEVICR = DEVICR_SUSPC; @@ -392,10 +342,10 @@ void dcd_int_handler(uint8_t rhport) dcd_event_bus_reset(rhport, get_speed(), true); } // End of Wakeup interrupt - if (int_status & DEVISR_WAKEUP) - { + if (int_status & DEVISR_WAKEUP) { USB_REG->CTRL &= ~CTRL_FRZCLK; - while (!(USB_REG->SR & SR_CLKUSABLE)); + while (!(USB_REG->SR & SR_CLKUSABLE)) + ; USB_REG->DEVICR = DEVICR_WAKEUPC; USB_REG->DEVIDR = DEVIDR_WAKEUPEC; USB_REG->DEVIER = DEVIER_SUSPES; @@ -403,11 +353,11 @@ void dcd_int_handler(uint8_t rhport) dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); } // Suspend interrupt - if (int_status & DEVISR_SUSP) - { + if (int_status & DEVISR_SUSP) { // Unfreeze USB clock USB_REG->CTRL &= ~CTRL_FRZCLK; - while (!(USB_REG->SR & SR_CLKUSABLE)); + while (!(USB_REG->SR & SR_CLKUSABLE)) + ; USB_REG->DEVICR = DEVICR_SUSPC; USB_REG->DEVIDR = DEVIDR_SUSPEC; USB_REG->DEVIER = DEVIER_WAKEUPES; @@ -415,29 +365,21 @@ void dcd_int_handler(uint8_t rhport) dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); } -#if USE_SOF - if(int_status & DEVISR_SOF) - { + if (int_status & DEVISR_SOF) { USB_REG->DEVICR = DEVICR_SOFC; dcd_event_bus_signal(0, DCD_EVENT_SOF, true); } -#endif // Endpoints interrupt - for (int ep_ix = 0; ep_ix < EP_MAX; ep_ix++) - { - if (int_status & (DEVISR_PEP_0 << ep_ix)) - { + for (int ep_ix = 0; ep_ix < EP_MAX; ep_ix++) { + if (int_status & (DEVISR_PEP_0 << ep_ix)) { dcd_ep_handler(ep_ix); } } // Endpoints DMA interrupt - for (int ep_ix = 0; ep_ix < EP_MAX; ep_ix++) - { - if (EP_DMA_SUPPORT(ep_ix)) - { - if (int_status & (DEVISR_DMA_1 << (ep_ix - 1))) - { + for (int ep_ix = 0; ep_ix < EP_MAX; ep_ix++) { + if (EP_DMA_SUPPORT(ep_ix)) { + if (int_status & (DEVISR_DMA_1 << (ep_ix - 1))) { dcd_dma_handler(ep_ix); } } @@ -449,35 +391,29 @@ void dcd_int_handler(uint8_t rhport) //--------------------------------------------------------------------+ // Invoked when a control transfer's status stage is complete. // May help DCD to prepare for next control transfer, this API is optional. -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) -{ - (void) rhport; +void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) { + (void)rhport; if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && - request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && - request->bRequest == TUSB_REQ_SET_ADDRESS ) - { - uint8_t const dev_addr = (uint8_t) request->wValue; + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) { + const uint8_t dev_addr = (uint8_t)request->wValue; USB_REG->DEVCTRL |= dev_addr | DEVCTRL_ADDEN; } } // Configure endpoint's registers according to descriptor -bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) -{ - (void) rhport; - uint8_t const epnum = tu_edpt_number(ep_desc->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(ep_desc->bEndpointAddress); - uint16_t const epMaxPktSize = tu_edpt_packet_size(ep_desc); - tusb_xfer_type_t const eptype = (tusb_xfer_type_t)ep_desc->bmAttributes.xfer; - uint8_t fifoSize = 0; // FIFO size - uint16_t defaultEndpointSize = 8; // Default size of Endpoint +bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) { + (void)rhport; + const uint8_t epnum = tu_edpt_number(ep_desc->bEndpointAddress); + const uint8_t dir = tu_edpt_dir(ep_desc->bEndpointAddress); + const uint16_t epMaxPktSize = tu_edpt_packet_size(ep_desc); + const tusb_xfer_type_t eptype = (tusb_xfer_type_t)ep_desc->bmAttributes.xfer; + uint8_t fifoSize = 0; // FIFO size + uint16_t defaultEndpointSize = 8; // Default size of Endpoint // Find upper 2 power number of epMaxPktSize - if (epMaxPktSize) - { - while (defaultEndpointSize < epMaxPktSize) - { + if (epMaxPktSize) { + while (defaultEndpointSize < epMaxPktSize) { fifoSize++; defaultEndpointSize <<= 1; } @@ -485,121 +421,94 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) xfer_status[epnum].max_packet_size = epMaxPktSize; USB_REG->DEVEPT |= 1 << (DEVEPT_EPRST0_Pos + epnum); - USB_REG->DEVEPT &=~(1 << (DEVEPT_EPRST0_Pos + epnum)); + USB_REG->DEVEPT &= ~(1 << (DEVEPT_EPRST0_Pos + epnum)); - if (epnum == 0) - { + if (epnum == 0) { // Enable the control endpoint - Endpoint 0 USB_REG->DEVEPT |= DEVEPT_EPEN0; // Configure the Endpoint 0 configuration register - USB_REG->DEVEPTCFG[0] = - ( - (fifoSize << DEVEPTCFG_EPSIZE_Pos) | - (TUSB_XFER_CONTROL << DEVEPTCFG_EPTYPE_Pos) | - (DEVEPTCFG_EPBK_1_BANK << DEVEPTCFG_EPBK_Pos) | - DEVEPTCFG_ALLOC - ); + USB_REG->DEVEPTCFG[0] = ((fifoSize << DEVEPTCFG_EPSIZE_Pos) | (TUSB_XFER_CONTROL << DEVEPTCFG_EPTYPE_Pos) | + (DEVEPTCFG_EPBK_1_BANK << DEVEPTCFG_EPBK_Pos) | DEVEPTCFG_ALLOC); USB_REG->DEVEPTIER[0] = DEVEPTIER_RSTDTS; USB_REG->DEVEPTIDR[0] = DEVEPTIDR_CTRL_STALLRQC; - if (DEVEPTISR_CFGOK == (USB_REG->DEVEPTISR[0] & DEVEPTISR_CFGOK)) - { + if (DEVEPTISR_CFGOK == (USB_REG->DEVEPTISR[0] & DEVEPTISR_CFGOK)) { // Endpoint configuration is successful USB_REG->DEVEPTIER[0] = DEVEPTIER_CTRL_RXSTPES; // Enable Endpoint 0 Interrupts USB_REG->DEVIER = DEVIER_PEP_0; return true; - } else - { + } else { // Endpoint configuration is not successful return false; } - } else - { + } else { // Enable the endpoint USB_REG->DEVEPT |= ((0x01 << epnum) << DEVEPT_EPEN0_Pos); // Set up the maxpacket size, fifo start address fifosize // and enable the interrupt. CLear the data toggle. // AUTOSW is needed for DMA ack ! USB_REG->DEVEPTCFG[epnum] = - ( - (fifoSize << DEVEPTCFG_EPSIZE_Pos) | - (eptype << DEVEPTCFG_EPTYPE_Pos) | - (DEVEPTCFG_EPBK_1_BANK << DEVEPTCFG_EPBK_Pos) | - DEVEPTCFG_AUTOSW | - ((dir & 0x01) << DEVEPTCFG_EPDIR_Pos) - ); - if (eptype == TUSB_XFER_ISOCHRONOUS) - { + ((fifoSize << DEVEPTCFG_EPSIZE_Pos) | (eptype << DEVEPTCFG_EPTYPE_Pos) | + (DEVEPTCFG_EPBK_1_BANK << DEVEPTCFG_EPBK_Pos) | DEVEPTCFG_AUTOSW | ((dir & 0x01) << DEVEPTCFG_EPDIR_Pos)); + if (eptype == TUSB_XFER_ISOCHRONOUS) { USB_REG->DEVEPTCFG[epnum] |= DEVEPTCFG_NBTRANS_1_TRANS; } -#if USE_DUAL_BANK - if (eptype == TUSB_XFER_ISOCHRONOUS || eptype == TUSB_XFER_BULK) - { + #if USE_DUAL_BANK + if (eptype == TUSB_XFER_ISOCHRONOUS || eptype == TUSB_XFER_BULK) { USB_REG->DEVEPTCFG[epnum] |= DEVEPTCFG_EPBK_2_BANK; } -#endif + #endif USB_REG->DEVEPTCFG[epnum] |= DEVEPTCFG_ALLOC; USB_REG->DEVEPTIER[epnum] = DEVEPTIER_RSTDTS; USB_REG->DEVEPTIDR[epnum] = DEVEPTIDR_CTRL_STALLRQC; - if (DEVEPTISR_CFGOK == (USB_REG->DEVEPTISR[epnum] & DEVEPTISR_CFGOK)) - { + if (DEVEPTISR_CFGOK == (USB_REG->DEVEPTISR[epnum] & DEVEPTISR_CFGOK)) { USB_REG->DEVIER = ((0x01 << epnum) << DEVIER_PEP_0_Pos); return true; - } else - { + } else { // Endpoint configuration is not successful return false; } } } -void dcd_edpt_close_all (uint8_t rhport) -{ - (void) rhport; +void dcd_edpt_close_all(uint8_t rhport) { + (void)rhport; // TODO implement dcd_edpt_close_all() } 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; + (void)rhport; + (void)ep_addr; + (void)largest_packet_size; return false; } -bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) { - (void) rhport; - (void) desc_ep; +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + (void)desc_ep; return false; } -static void dcd_transmit_packet(xfer_ctl_t * xfer, uint8_t ep_ix) -{ +static void dcd_transmit_packet(xfer_ctl_t *xfer, uint8_t ep_ix) { uint16_t len = (uint16_t)(xfer->total_len - xfer->queued_len); - if (len) - { - if (len > xfer->max_packet_size) - { + if (len) { + if (len > xfer->max_packet_size) { len = xfer->max_packet_size; } - uint8_t *ptr = EP_GET_FIFO_PTR(ep_ix,8); - if(xfer->buffer) - { + uint8_t *ptr = EP_GET_FIFO_PTR(ep_ix, 8); + if (xfer->buffer) { memcpy(ptr, xfer->buffer + xfer->queued_len, len); - } - else - { - tu_fifo_read_n(xfer->fifo, ptr, len); + } else { + tu_hwfifo_write_from_fifo(ptr, xfer->fifo, len, NULL); } __DSB(); __ISB(); xfer->queued_len = (uint16_t)(xfer->queued_len + len); } - if (ep_ix == 0U) - { + if (ep_ix == 0U) { // Control endpoint: clear the interrupt flag to send the data USB_REG->DEVEPTICR[0] = DEVEPTICR_TXINIC; - } else - { + } else { // Other endpoint types: clear the FIFO control flag to send the data USB_REG->DEVEPTIDR[ep_ix] = DEVEPTIDR_FIFOCONC; } @@ -607,59 +516,36 @@ static void dcd_transmit_packet(xfer_ctl_t * xfer, uint8_t ep_ix) } // Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) -{ - (void) is_isr; - (void) rhport; - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { + (void)is_isr; + (void)rhport; + const uint8_t epnum = tu_edpt_number(ep_addr); + const uint8_t dir = tu_edpt_dir(ep_addr); - xfer_ctl_t * xfer = &xfer_status[epnum]; + xfer_ctl_t *xfer = &xfer_status[epnum]; - xfer->buffer = buffer; - xfer->total_len = total_bytes; + xfer->buffer = buffer; + xfer->total_len = total_bytes; xfer->queued_len = 0; - xfer->fifo = NULL; + xfer->fifo = NULL; - if (EP_DMA_SUPPORT(epnum) && total_bytes != 0) - { - // Force the CPU to flush the buffer. We increase the size by 32 because the call aligns the - // address to 32-byte boundaries. - CleanInValidateCache((uint32_t*) tu_align((uint32_t) buffer, 4), total_bytes + 31); + if (EP_DMA_SUPPORT(epnum) && total_bytes != 0) { uint32_t udd_dma_ctrl = total_bytes << DEVDMACONTROL_BUFF_LENGTH_Pos; - if (dir == TUSB_DIR_OUT) - { + if (dir == TUSB_DIR_OUT) { udd_dma_ctrl |= DEVDMACONTROL_END_TR_IT | DEVDMACONTROL_END_TR_EN; } else { udd_dma_ctrl |= DEVDMACONTROL_END_B_EN; + dcd_dcache_clean(xfer->buffer, total_bytes); } USB_REG->DEVDMA[epnum - 1].DEVDMAADDRESS = (uint32_t)buffer; udd_dma_ctrl |= DEVDMACONTROL_END_BUFFIT | DEVDMACONTROL_CHANN_ENB; - // Disable IRQs to have a short sequence - // between read of EOT_STA and DMA enable - uint32_t irq_state = __get_PRIMASK(); - __disable_irq(); - if (!(USB_REG->DEVDMA[epnum - 1].DEVDMASTATUS & DEVDMASTATUS_END_TR_ST)) - { - USB_REG->DEVDMA[epnum - 1].DEVDMACONTROL = udd_dma_ctrl; - USB_REG->DEVIER = DEVIER_DMA_1 << (epnum - 1); - __set_PRIMASK(irq_state); - return true; - } - __set_PRIMASK(irq_state); - - // Here a ZLP has been received - // and the DMA transfer must be not started. - // It is the end of transfer - return false; - } else - { - if (dir == TUSB_DIR_OUT) - { + USB_REG->DEVDMA[epnum - 1].DEVDMACONTROL = udd_dma_ctrl; + USB_REG->DEVIER = DEVIER_DMA_1 << (epnum - 1); + } else { + if (dir == TUSB_DIR_OUT) { USB_REG->DEVEPTIER[epnum] = DEVEPTIER_RXOUTES; - } else - { - dcd_transmit_packet(xfer,epnum); + } else { + dcd_transmit_packet(xfer, epnum); } } return true; @@ -669,112 +555,43 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t // bytes should be written and second to keep the return value free to give back a boolean // success message. If total_bytes is too big, the FIFO will copy only what is available // into the USB buffer! -bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes, bool is_isr) -{ - (void) is_isr; - (void) rhport; - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); +bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t total_bytes, bool is_isr) { + (void)is_isr; + (void)rhport; + const uint8_t epnum = tu_edpt_number(ep_addr); + const uint8_t dir = tu_edpt_dir(ep_addr); - xfer_ctl_t * xfer = &xfer_status[epnum]; - if(epnum == 0x80) - xfer = &xfer_status[EP_MAX]; + xfer_ctl_t *xfer = &xfer_status[epnum]; - xfer->buffer = NULL; - xfer->total_len = total_bytes; + xfer->buffer = NULL; + xfer->total_len = total_bytes; xfer->queued_len = 0; - xfer->fifo = ff; - - if (EP_DMA_SUPPORT(epnum) && total_bytes != 0) - { - tu_fifo_buffer_info_t info; - uint32_t udd_dma_ctrl_lin = DEVDMACONTROL_CHANN_ENB; - uint32_t udd_dma_ctrl_wrap = DEVDMACONTROL_CHANN_ENB | DEVDMACONTROL_END_BUFFIT; - if (dir == TUSB_DIR_OUT) - { - tu_fifo_get_write_info(ff, &info); - udd_dma_ctrl_lin |= DEVDMACONTROL_END_TR_IT | DEVDMACONTROL_END_TR_EN; - udd_dma_ctrl_wrap |= DEVDMACONTROL_END_TR_IT | DEVDMACONTROL_END_TR_EN; - } else { - tu_fifo_get_read_info(ff, &info); - if(info.wrapped.len == 0) - { - udd_dma_ctrl_lin |= DEVDMACONTROL_END_B_EN; - } - udd_dma_ctrl_wrap |= DEVDMACONTROL_END_B_EN; - } - - // Clean invalidate cache of linear part - CleanInValidateCache((uint32_t*) tu_align((uint32_t) info.linear.ptr, 4), info.linear.len + 31); - - USB_REG->DEVDMA[epnum - 1].DEVDMAADDRESS = (uint32_t)info.linear.ptr; - if (info.wrapped.len) - { - // Clean invalidate cache of wrapped part - CleanInValidateCache((uint32_t*) tu_align((uint32_t) info.wrapped.ptr, 4), info.wrapped.len + 31); + xfer->fifo = ff; - dma_desc[epnum - 1].next_desc = 0; - dma_desc[epnum - 1].buff_addr = (uint32_t)info.wrapped.ptr; - dma_desc[epnum - 1].chnl_ctrl = - udd_dma_ctrl_wrap | (info.wrapped.len << DEVDMACONTROL_BUFF_LENGTH_Pos); - // Clean cache of wrapped DMA descriptor - CleanInValidateCache((uint32_t*)&dma_desc[epnum - 1], sizeof(dma_desc_t)); - - udd_dma_ctrl_lin |= DEVDMASTATUS_DESC_LDST; - USB_REG->DEVDMA[epnum - 1].DEVDMANXTDSC = (uint32_t)&dma_desc[epnum - 1]; - } else { - udd_dma_ctrl_lin |= DEVDMACONTROL_END_BUFFIT; - } - udd_dma_ctrl_lin |= (info.linear.len << DEVDMACONTROL_BUFF_LENGTH_Pos); - // Disable IRQs to have a short sequence - // between read of EOT_STA and DMA enable - uint32_t irq_state = __get_PRIMASK(); - __disable_irq(); - if (!(USB_REG->DEVDMA[epnum - 1].DEVDMASTATUS & DEVDMASTATUS_END_TR_ST)) - { - USB_REG->DEVDMA[epnum - 1].DEVDMACONTROL = udd_dma_ctrl_lin; - USB_REG->DEVIER = DEVIER_DMA_1 << (epnum - 1); - __set_PRIMASK(irq_state); - return true; - } - __set_PRIMASK(irq_state); - - // Here a ZLP has been received - // and the DMA transfer must be not started. - // It is the end of transfer - return false; - } else - { - if (dir == TUSB_DIR_OUT) - { - USB_REG->DEVEPTIER[epnum] = DEVEPTIER_RXOUTES; - } else - { - dcd_transmit_packet(xfer,epnum); - } + if (dir == TUSB_DIR_OUT) { + USB_REG->DEVEPTIER[epnum] = DEVEPTIER_RXOUTES; + } else { + dcd_transmit_packet(xfer, epnum); } return true; } // Stall endpoint -void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - uint8_t const epnum = tu_edpt_number(ep_addr); +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { + (void)rhport; + const uint8_t epnum = tu_edpt_number(ep_addr); USB_REG->DEVEPTIER[epnum] = DEVEPTIER_CTRL_STALLRQS; // Re-enable SETUP interrupt - if (epnum == 0) - { + if (epnum == 0) { USB_REG->DEVEPTIER[0] = DEVEPTIER_CTRL_RXSTPES; } } // clear stall, data toggle is also reset to DATA0 -void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - uint8_t const epnum = tu_edpt_number(ep_addr); +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { + (void)rhport; + const uint8_t epnum = tu_edpt_number(ep_addr); USB_REG->DEVEPTIDR[epnum] = DEVEPTIDR_CTRL_STALLRQC; - USB_REG->DEVEPTIER[epnum] = HSTPIPIER_RSTDTS; + USB_REG->DEVEPTIER[epnum] = DEVEPTIER_RSTDTS; } #endif diff --git a/src/portable/microchip/samx7x/common_usb_regs.h b/src/portable/microchip/samx7x/samx7x_common.h index db4a81e0e..5e3c20c0f 100644 --- a/src/portable/microchip/samx7x/common_usb_regs.h +++ b/src/portable/microchip/samx7x/samx7x_common.h @@ -1,4 +1,4 @@ - /* +/* * The MIT License (MIT) * * Copyright (c) 2019 Microchip Technology Inc. @@ -2098,7 +2098,72 @@ typedef struct #define FIFO_RAM_ADDR 0xA0100000u // Errata: The DMA feature is not available for Pipe/Endpoint 7 -#define EP_DMA_SUPPORT(epnum) (epnum >= 1 && epnum <= 6) +#define EP_DMA_SUPPORT(epnum) (epnum >= 1 && epnum <= 6 && CFG_TUD_SAMX7X_DMA_ENABLE) + +//------------- DCache -------------// +#if CFG_TUD_MEM_DCACHE_ENABLE || CFG_TUH_MEM_DCACHE_ENABLE + +typedef struct { + uintptr_t start; + uintptr_t end; +} mem_region_t; + +// Can be used to define additional uncached regions +#ifndef CFG_SAMX7X_MEM_UNCACHED_REGIONS +#define CFG_SAMX7X_MEM_UNCACHED_REGIONS +#endif + +static mem_region_t uncached_regions[] = { + // DTCM + {.start = 0x20000000, .end = 0x203fffff}, + CFG_SAMX7X_MEM_UNCACHED_REGIONS +}; + +TU_ATTR_ALWAYS_INLINE static inline uint32_t round_up_to_cache_line_size(uint32_t size) { + if (size & (CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT-1)) { + size = (size & ~(CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT-1)) + CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT; + } + return size; +} + +TU_ATTR_ALWAYS_INLINE static inline bool is_cache_mem(uintptr_t addr) { + if (0 == (SCB->CCR & SCB_CCR_DC_Msk)) { + return false; // D-Cache is disabled + } + for (unsigned int i = 0; i < TU_ARRAY_SIZE(uncached_regions); i++) { + if (uncached_regions[i].start <= addr && addr <= uncached_regions[i].end) { return false; } + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool samx7x_dcache_clean(void const* addr, uint32_t data_size) { + const uintptr_t addr32 = (uintptr_t) addr; + if (is_cache_mem(addr32)) { + data_size = round_up_to_cache_line_size(data_size); + SCB_CleanDCache_by_Addr((uint32_t *) addr32, (int32_t) data_size); + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool samx7x_dcache_invalidate(void const* addr, uint32_t data_size) { + const uintptr_t addr32 = (uintptr_t) addr; + if (is_cache_mem(addr32)) { + data_size = round_up_to_cache_line_size(data_size); + SCB_InvalidateDCache_by_Addr((void*) addr32, (int32_t) data_size); + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool samx7x_dcache_clean_invalidate(void const* addr, uint32_t data_size) { + const uintptr_t addr32 = (uintptr_t) addr; + if (is_cache_mem(addr32)) { + data_size = round_up_to_cache_line_size(data_size); + SCB_CleanInvalidateDCache_by_Addr((uint32_t *) addr32, (int32_t) data_size); + } + return true; +} + +#endif #else // TODO : SAM3U diff --git a/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c new file mode 100644 index 000000000..c9810f51c --- /dev/null +++ b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c @@ -0,0 +1,821 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 HiFiPhile (Zixun LI) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && defined(TUP_USBIP_IP3516) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "common/tusb_common.h" +#include "host/hcd.h" +#include "host/usbh.h" +#include "hcd_lpc_ip3516.h" + +#if TU_CHECK_MCU(OPT_MCU_LPC55, OPT_MCU_LPC54) + #include "fsl_device_registers.h" +#else + #error "Unsupported MCUs" +#endif + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +#if TU_CHECK_MCU(OPT_MCU_LPC54) + #define ATLPTD ATL_PTD_BASE_ADDR + #define INTPTD INT_PTD_BASE_ADDR + #define ISOPTD ISO_PTD_BASE_ADDR + #define ATLPTDD ATL_PTD_DONE_MAP + #define INTPTDD INT_PTD_DONE_MAP + #define ISOPTDD ISO_PTD_DONE_MAP + #define ATLPTDS ATL_PTD_SKIP_MAP + #define INTPTDS INT_PTD_SKIP_MAP + #define ISOPTDS ISO_PTD_SKIP_MAP + #define DATAPAYLOAD DATA_PAYLOAD_BASE_ADDR + #define LASTPTD LAST_PTD_INUSE + + #define USBHSH_ATLPTD_ATL_BASE_MASK USBHSH_ATL_PTD_BASE_ADDR_ATL_BASE_MASK + #define USBHSH_INTPTD_INT_BASE_MASK USBHSH_INT_PTD_BASE_ADDR_INT_BASE_MASK + #define USBHSH_ISOPTD_ISO_BASE_MASK USBHSH_ISO_PTD_BASE_ADDR_ISO_BASE_MASK + + #define USBHSH_DATAPAYLOAD_DAT_BASE_MASK USBHSH_DATA_PAYLOAD_BASE_ADDR_DAT_BASE_MASK + + #define USBHSH_LASTPTD_ATL_LAST USBHSH_LAST_PTD_INUSE_ATL_LAST + #define USBHSH_LASTPTD_INT_LAST USBHSH_LAST_PTD_INUSE_INT_LAST + #define USBHSH_LASTPTD_ISO_LAST USBHSH_LAST_PTD_INUSE_ISO_LAST +#endif + +#define USBHSH_PORTSC1_W1C_MASK (USBHSH_PORTSC1_CSC_MASK | USBHSH_PORTSC1_PEDC_MASK | USBHSH_PORTSC1_OCC_MASK) + +#define IP3516_PSPD_LOW 0 +#define IP3516_PSPD_FULL 1 +#define IP3516_PSPD_HIGH 2 + +//--------------------------------------------------------------------+ +// Proprietary Transfer Descriptor +//--------------------------------------------------------------------+ + +CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(1024) static ip3516_ptd_t _ptd; + +static struct { + uint32_t uframe_number; + uint32_t uframe_length; + bool attached; // Track attachment state to avoid duplicate events, sometimes high-speed disconnection detector is not reliable +} _hcd_data; + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +static inline bool is_ptd_free(const ptd_ctrl1_t ctrl1) { + return ctrl1.mps == 0; +} + +static inline bool is_xfer_async(tusb_xfer_type_t xfer_type) { + return (xfer_type == TUSB_XFER_CONTROL || xfer_type == TUSB_XFER_BULK); +} + +static inline void ptd_clear_state(ptd_state_t *state) { + ptd_state_t local = {.value = 0}; + local.ep_type = state->ep_type; // preserve ep_type + local.token = state->token; // preserve token + local.data_toggle = state->data_toggle; // preserve data_toggle + *state = local; +} + +static inline uint8_t ptd_find_free(tusb_xfer_type_t xfer_type) { + uint8_t max_count; + intptr_t ptd_array; + + switch (xfer_type) { + case TUSB_XFER_CONTROL: + case TUSB_XFER_BULK: + max_count = IP3516_ATL_NUM; + ptd_array = (intptr_t)&_ptd.atl; + break; + + case TUSB_XFER_INTERRUPT: + max_count = IP3516_PTL_NUM; + ptd_array = (intptr_t)&_ptd.intr; + break; + + case TUSB_XFER_ISOCHRONOUS: + max_count = IP3516_PTL_NUM; + ptd_array = (intptr_t)&_ptd.iso; + break; + + default: + return TUSB_INDEX_INVALID_8; + } + + for (uint8_t i = 0; i < max_count; i++) { + // For ATL: stride is sizeof(ip3516_atl_t) = 16 bytes = 4 words + // For PTL: stride is sizeof(ip3516_ptl_t) = 32 bytes = 8 words + uint8_t stride = is_xfer_async(xfer_type) ? sizeof(ip3516_atl_t) : sizeof(ip3516_ptl_t); + ptd_ctrl1_t *ctrl1 = (ptd_ctrl1_t *)(ptd_array + i * stride); + + if (is_ptd_free(*ctrl1)) { + return i; + } + } + + return TUSB_INDEX_INVALID_8; // No free PTD found +} + +// Close all PTDs associated with a specific device address +static void close_ptds_by_device(uint8_t dev_addr, intptr_t ptd_array, uint8_t max_count, uint8_t stride, + volatile uint32_t *skip_reg) { + + uint32_t skip_mask = 0; + + for (uint8_t i = 0; i < max_count; i++) { + intptr_t ptd_ptr = ptd_array + i * stride; + ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1)); + ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2)); + + if (!is_ptd_free(*ptd_ctrl1) && ptd_ctrl2->dev_addr == dev_addr) { + *skip_reg |= (1 << i); + skip_mask |= (1 << i); + } + } + + if (skip_mask) { + // Wait 1 uframe for PTDs to be inactive (with timeout) + uint32_t start_uframe = + (USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT; + uint32_t timeout = 10000; + while (((USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT) == + start_uframe && timeout > 0) { + timeout--; + } + + // Clear PTDs + for (uint8_t i = 0; i < max_count; i++) { + if (skip_mask & (1 << i)) { + intptr_t ptd_ptr = ptd_array + i * stride; + tu_memclr((void *)ptd_ptr, stride); + } + } + + // Clear skip bits + *skip_reg &= ~skip_mask; + } +} + +// Check if a PTD matches the given endpoint criteria +static bool ptd_matches(intptr_t ptd_ptr, uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir) { + ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1)); + if (is_ptd_free(*ptd_ctrl1)) { + return false; + } + + ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2)); + if (ptd_ctrl2->dev_addr != dev_addr || ptd_ctrl2->ep_num != ep_num) { + return false; + } + + ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state)); + bool is_control = (ptd_state->ep_type == TUSB_XFER_CONTROL); + + // For control endpoint, match both IN and OUT directions + if (is_control) { + return true; + } + + if (ep_dir == TUSB_DIR_IN && ptd_state->token == IP3516_PTD_TOKEN_IN) { + return true; + } + + if (ep_dir == TUSB_DIR_OUT && ptd_state->token == IP3516_PTD_TOKEN_OUT) { + return true; + } + + return false; +} + +// Find and close a specific PTD +static bool find_and_close_ptd(uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir, intptr_t ptd_array, uint8_t max_count, + uint8_t stride, volatile uint32_t *skip_reg) { + for (uint8_t i = 0; i < max_count; i++) { + intptr_t ptd_ptr = ptd_array + i * stride; + if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) { + if (skip_reg) { + *skip_reg |= (1 << i); + + // Wait 1 uframe for PTD to be inactive (with timeout) + uint32_t start_uframe = + (USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT; + uint32_t timeout = 10000; + while (((USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT) == + start_uframe && timeout > 0) { + timeout--; + } + + // Just clear state + ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1)); + ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state)); + ptd_clear_state(ptd_state); + ptd_ctrl1->valid = 0; + + *skip_reg &= ~(1 << i); + } else { + // Clear PTD + tu_memclr((void *)ptd_ptr, stride); + } + return true; + } + } + return false; +} + +// Find an opened PTD +static intptr_t find_opened_ptd(uint8_t dev_addr, uint8_t ep_addr) { + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = tu_edpt_dir(ep_addr); + + // Search in ATL + for (uint8_t i = 0; i < IP3516_ATL_NUM; i++) { + intptr_t ptd_ptr = (intptr_t)&_ptd.atl[i]; + if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) { + return ptd_ptr; + } + } + + // Search in INT + for (uint8_t i = 0; i < IP3516_PTL_NUM; i++) { + intptr_t ptd_ptr = (intptr_t)&_ptd.intr[i]; + if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) { + return ptd_ptr; + } + } + + // Search in ISO + for (uint8_t i = 0; i < IP3516_PTL_NUM; i++) { + intptr_t ptd_ptr = (intptr_t)&_ptd.iso[i]; + if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) { + return ptd_ptr; + } + } + + return 0; +} + +static bool edpt_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen, bool is_setup) { + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = tu_edpt_dir(ep_addr); + + intptr_t ptd_ptr = find_opened_ptd(dev_addr, ep_addr); + TU_ASSERT(ptd_ptr != 0); + + ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1)); + ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2)); + ptd_data_t *ptd_data = (ptd_data_t *)(ptd_ptr + offsetof(ip3516_atl_t, data)); + ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state)); + + // Setup data buffer and length + ptd_data->data_addr = (uint32_t)(uintptr_t)buffer & IP3516_PTD_DATA_ADDR_MASK; + ptd_data->xfer_len = buflen; + + // Clear previous state + ptd_clear_state(ptd_state); + + // Set token for EP0 + if (ep_num == 0) { + if (is_setup) { + ptd_state->token = IP3516_PTD_TOKEN_SETUP; + ptd_state->data_toggle = 0; + } else { + ptd_state->token = (ep_dir == TUSB_DIR_IN) ? IP3516_PTD_TOKEN_IN : IP3516_PTD_TOKEN_OUT; + ptd_state->data_toggle = 1; + } + } + + // Interrupt split transfer needs to be relaunched manually if NAKed + if (ptd_ctrl2->split && ptd_state->ep_type == TUSB_XFER_INTERRUPT) { + ptd_ctrl2->reload = 0x0f; + ptd_state->nak_cnt = 0x0f; + } + + // Activate PTD + ptd_ctrl1->valid = 1; + ptd_state->active = 1; + + return true; +} + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// Initialize controller to host mode +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rh_init; + (void)rhport; + + // Reset controller + USBHSH->USBCMD |= USBHSH_USBCMD_HCRESET_MASK; + while (USBHSH->USBCMD & USBHSH_USBCMD_HCRESET_MASK) {} + + USBHSH->PORTMODE = USBHSH_PORTMODE_SW_CTRL_PDCOM_MASK; + + tu_memclr(&_ptd, sizeof(_ptd)); + tu_varclr(&_hcd_data); + + // Set base addresses + USBHSH->ATLPTD = (uint32_t)&_ptd.atl & USBHSH_ATLPTD_ATL_BASE_MASK; + USBHSH->INTPTD = (uint32_t)&_ptd.intr & USBHSH_INTPTD_INT_BASE_MASK; + USBHSH->ISOPTD = (uint32_t)&_ptd.iso & USBHSH_ISOPTD_ISO_BASE_MASK; + USBHSH->DATAPAYLOAD = (uint32_t)&_ptd & USBHSH_DATAPAYLOAD_DAT_BASE_MASK; + + // Turn on power switch + if (USBHSH->HCSPARAMS & USBHSH_HCSPARAMS_PPC_MASK) { + USBHSH->PORTSC1 |= USBHSH_PORTSC1_PP_MASK; + } + + // Get frame list size + uint32_t fls = (USBHSH->USBCMD & USBHSH_USBCMD_FLS_MASK) >> USBHSH_USBCMD_FLS_SHIFT; + _hcd_data.uframe_length = 8192 >> fls; + + // Clear pending interrupts + USBHSH->USBSTS = 0xFFFFFFFF; + + // Enable interrupts + USBHSH->USBINTR = USBHSH_USBINTR_ATL_IRQ_E_MASK | USBHSH_USBINTR_INT_IRQ_E_MASK | USBHSH_USBINTR_ISO_IRQ_E_MASK | + USBHSH_USBINTR_PCDE_MASK | USBHSH_USBINTR_FLRE_MASK; + + + // Enable all PTDs + USBHSH->LASTPTD = USBHSH_LASTPTD_ATL_LAST(IP3516_ATL_NUM - 1) | USBHSH_LASTPTD_INT_LAST(IP3516_PTL_NUM - 1) | + USBHSH_LASTPTD_ISO_LAST(IP3516_PTL_NUM - 1); + + // Enable controller + USBHSH->USBCMD = USBHSH_USBCMD_ATL_EN_MASK | USBHSH_USBCMD_INT_EN_MASK | USBHSH_USBCMD_ISO_EN_MASK | USBHSH_USBCMD_RS_MASK; + + return true; +} + +// Enable USB interrupt +void hcd_int_enable(uint8_t rhport) { + (void)rhport; + NVIC_EnableIRQ(USB1_IRQn); +} + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport) { + (void)rhport; + NVIC_DisableIRQ(USB1_IRQn); +} + +bool hcd_deinit(uint8_t rhport) { + (void)rhport; + + // Disable interrupts + USBHSH->USBINTR = 0; + USBHSH->USBSTS = 0xFFFFFFFF; + + // Disable controller + USBHSH->USBCMD &= ~(USBHSH_USBCMD_ATL_EN_MASK | USBHSH_USBCMD_INT_EN_MASK | USBHSH_USBCMD_ISO_EN_MASK | USBHSH_USBCMD_RS_MASK); + + // Turn off power switch + if (USBHSH->HCSPARAMS & USBHSH_HCSPARAMS_PPC_MASK) { + USBHSH->PORTSC1 &= ~USBHSH_PORTSC1_PP_MASK; + } + + // Connect PHY to device mode + USBHSH->PORTMODE = USBHSH_PORTMODE_SW_CTRL_PDCOM_MASK | USBHSH_PORTMODE_DEV_ENABLE_MASK; + + return true; +} + +//--------------------------------------------------------------------+ +// Port API +//--------------------------------------------------------------------+ + +// Reset USB bus on the port. Return immediately, bus reset sequence may not be complete. +// Some port would require hcd_port_reset_end() to be invoked after 10ms to complete the reset sequence. +void hcd_port_reset(uint8_t rhport) { + (void)rhport; + uint32_t status = USBHSH->PORTSC1 & ~USBHSH_PORTSC1_W1C_MASK; + USBHSH->PORTSC1 = status | USBHSH_PORTSC1_PR_MASK; +} + +// Complete bus reset sequence, may be required by some controllers +void hcd_port_reset_end(uint8_t rhport) { + (void)rhport; + uint32_t status = USBHSH->PORTSC1 & ~USBHSH_PORTSC1_W1C_MASK; + USBHSH->PORTSC1 = status & ~USBHSH_PORTSC1_PR_MASK; + while (USBHSH->PORTSC1 & USBHSH_PORTSC1_PR_MASK) {} +#if ((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U)) + uint32_t pspd = (USBHSH->PORTSC1 & USBHSH_PORTSC1_PSPD_MASK) >> USBHSH_PORTSC1_PSPD_SHIFT; + if (pspd == IP3516_PSPD_HIGH) { + // enable phy disconnection for high speed + USBPHY->CTRL |= USBPHY_CTRL_ENHOSTDISCONDETECT_MASK; + } +#endif +} + +// Get the current connect status of roothub port +bool hcd_port_connect_status(uint8_t rhport) { + (void)rhport; + return (USBHSH->PORTSC1 & USBHSH_PORTSC1_CCS_MASK) ? true : false; +} + +// Get port link speed +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + (void)rhport; + uint32_t pspd = (USBHSH->PORTSC1 & USBHSH_PORTSC1_PSPD_MASK) >> USBHSH_PORTSC1_PSPD_SHIFT; + switch (pspd) { + case IP3516_PSPD_LOW: + return TUSB_SPEED_LOW; + case IP3516_PSPD_FULL: + return TUSB_SPEED_FULL; + case IP3516_PSPD_HIGH: + return TUSB_SPEED_HIGH; + default: + return TUSB_SPEED_INVALID; + } +} + +// Get frame number (1ms) +uint32_t hcd_frame_number(uint8_t rhport) { + (void)rhport; + uint32_t uframe = (USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT; + uframe &= (_hcd_data.uframe_length - 1); + return (uframe + _hcd_data.uframe_number) >> 3; +} + +// HCD closes all opened endpoints belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { + (void)rhport; + + close_ptds_by_device(dev_addr, (intptr_t)&_ptd.atl, IP3516_ATL_NUM, sizeof(ip3516_atl_t), &USBHSH->ATLPTDS); + close_ptds_by_device(dev_addr, (intptr_t)&_ptd.intr, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), &USBHSH->INTPTDS); + close_ptds_by_device(dev_addr, (intptr_t)&_ptd.iso, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), &USBHSH->ISOPTDS); +} + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ + +static inline intptr_t get_ptd_from_index(tusb_xfer_type_t xfer_type, uint8_t ptd_index) { + if (is_xfer_async(xfer_type)) { + return (intptr_t)&_ptd.atl[ptd_index]; + } else { + if (xfer_type == TUSB_XFER_INTERRUPT) { + return (intptr_t)&_ptd.intr[ptd_index]; + } else { + return (intptr_t)&_ptd.iso[ptd_index]; + } + } +} + + +// Open an endpoint +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { + (void)rhport; + + const uint8_t ep_num = tu_edpt_number(ep_desc->bEndpointAddress); + const tusb_xfer_type_t xfer_type = (tusb_xfer_type_t)ep_desc->bmAttributes.xfer; + + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); + + // Find a free PTD + uint8_t ptd_index = ptd_find_free(xfer_type); + TU_ASSERT(ptd_index != TUSB_INDEX_INVALID_8); + + // Configure PTD + intptr_t ptd_ptr = get_ptd_from_index(xfer_type, ptd_index); + volatile ptd_ctrl1_t *ctrl1 = (volatile ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1)); + volatile ptd_ctrl2_t *ctrl2 = (volatile ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2)); + volatile ptd_data_t *data = (volatile ptd_data_t *)(ptd_ptr + offsetof(ip3516_atl_t, data)); + volatile ptd_state_t *state = (volatile ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state)); + + // Initialize PTD fields + ctrl1->mps = ep_desc->wMaxPacketSize; + ctrl1->mult = 1; + + ctrl2->dev_addr = dev_addr; + ctrl2->ep_num = ep_num; + ctrl2->speed = bus_info.speed == TUSB_SPEED_LOW ? 2 : 0; + ctrl2->hub_addr = bus_info.hub_addr; + ctrl2->hub_port = bus_info.hub_port; + ctrl2->split = (hcd_port_speed_get(rhport) == TUSB_SPEED_HIGH) && (bus_info.speed != TUSB_SPEED_HIGH) ? 1 : 0; + + data->intr = 1; + + state->ep_type = (uint32_t)xfer_type; + state->token = tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN ? IP3516_PTD_TOKEN_IN : IP3516_PTD_TOKEN_OUT; + + if (!is_xfer_async(xfer_type)) { + ip3516_ptl_t *ptd = (ip3516_ptl_t *)ptd_ptr; + + uint32_t uframe_interval; + if (bus_info.speed == TUSB_SPEED_HIGH) { + uframe_interval = 1 << (ep_desc->bInterval - 1); + } else { + uframe_interval = ep_desc->bInterval << 3; + // round down to nearest power of 2 + uframe_interval = 1 << tu_log2(uframe_interval); + } + uframe_interval = tu_min32(uframe_interval, IP3516_MAX_UFRAME); + + // uframe_active is an 8-bit mask, where each bit corresponds to a micro-frame within a 1ms frame. + // A '1' indicates the endpoint should be polled in that micro-frame. + // For example: + // Interval 1 (poll every u-frame) -> mask is 0b11111111 (0xFF) + // Interval 2 (poll every 2nd u-frame, e.g., 0, 2, 4, 6) -> mask is 0b10101010 (0xAA) + // Interval 4 (poll every 4th u-frame, e.g., 0, 4) -> mask is 0b10001000 (0x88) + // Interval 8 (poll every 8th u-frame, e.g., 0) -> mask is 0b10000000 (0x80) + switch (uframe_interval) { + case 1: + ptd->status.uframe_active = 0xFF; + break; + case 2: + ptd->status.uframe_active = 0xAA; + break; + case 4: + ptd->status.uframe_active = 0x11; + break; + case 8: + ptd->status.uframe_active = 0x01; + break; + default: + // For intervals > 8, we poll once per frame (every 8 u-frames) and use ctrl1.uframe to skip frames. + ptd->status.uframe_active = 0x01; + if (uframe_interval >= 16) { + ctrl1->uframe = tu_log2(uframe_interval) - 3; + } + break; + } + + if (ctrl2->split) { + // 11.18.1 Best Case Full-Speed Budget + // + // A microframe of time allows at most 187.5 raw bytes of signaling on a full-speed bus. + // The best case full-speed budget assumes that 188 full-speed bytes occur in each microframe. + // + // A 1 ms frame subdivided into microframes of budget time: + // + // Microframes Y_0 Y_1 Y_2 Y_3 Y_4 Y_5 Y_6 Y_7 + // Max wire time 187.5 187.5 187.5 187.5 187.5 187.5 32 + // Best case wire budget 188 188 188 188 188 188 29 + // + // 11.18.4 Host Split Transaction Scheduling Requirements + // + // 1. The host must never schedule a start-split in microframe Y_6. + // 2. For isochronous OUT full-speed transactions, for each microframe in which the transaction is + // budgeted, the host must schedule a 188 (or the remaining data size) data byte start-split transaction. + // For isochronous IN and interrupt IN/OUT full-/low-speed transactions, a single start-split must be + // scheduled in the microframe before the transaction is budgeted to start on the full-/low-speed bus. + // 3. For isochronous OUT full-speed transactions, the host must never schedule a complete-split. The + // TT response to a complete-split for an isochronous OUT is undefined. + // For interrupt IN/OUT full-/low-speed transactions, the host must schedule a complete-split + // transaction in each of the two microframes following the first microframe in which the full-/low- + // speed transaction is budgeted. An additional complete-split must also be scheduled in the third + // following microframe unless the full-/low-speed transaction was budgeted to start in microframe Y_6 + // For isochronous IN full-speed transactions, for each microframe in which the full-speed transaction + // is budgeted, a complete-split must be scheduled for each following microframe. + // Also, determine the last microframe in which a complete-split is scheduled, call it L. + // If L is less than Y_6, schedule additional complete-splits in microframe L+1 and L+2. + // If L is equal to Y_6, schedule one complete-split in microframe Y_7. + // + // TODO: Implement budget check scheduling + // Otherwise, it may cause bus contention with other split transfers + // Here we simply start interrupt transfers for Y_0 and Y1 and isochronous transfers for Y_2 + if (xfer_type == TUSB_XFER_ISOCHRONOUS) { + const uint8_t ss_slot = 2; // Start-split slot + const uint8_t slots = (ep_desc->wMaxPacketSize + 187) / 188; + const tusb_dir_t ep_dir = tu_edpt_dir(ep_desc->bEndpointAddress); + if (ep_dir == TUSB_DIR_IN) { + if (ep_desc->wMaxPacketSize > 192) { + ctrl1->mps = 192; + } + ptd->status.uframe_active = 1 << ss_slot; + for (uint8_t i = 0; i < slots; i++) { + ptd->iso_in_0.uframe_complete |= 1 << (2 + ss_slot + i); + } + // Schedule additional complete-splits if needed + uint8_t last_complete = ss_slot + slots + 1; + if (last_complete < 6) { + ptd->iso_in_0.uframe_complete |= 1 << (ss_slot + last_complete + 1); + ptd->iso_in_0.uframe_complete |= 1 << (ss_slot + last_complete + 2); + } else if (last_complete == 6) { + ptd->iso_in_0.uframe_complete |= 1 << 7; + } + } else { + if (ep_desc->wMaxPacketSize > 188) { + ctrl1->mps = 188; + } + for (uint8_t i = 0; i < slots; i++) { + ptd->status.uframe_active |= 1 << (ss_slot + i); + } + } + } else { + // Start-split slot, jigging to avoid bus contention: EP odd -> Y_1, EP even -> Y_0 + const uint8_t ss_slot = ep_num & 0x01; + ptd->status.uframe_active = 1 << ss_slot; + // Complete-split slots: next 3 u-frames + ptd->iso_in_0.uframe_complete = 0x1c << ss_slot; + } + } + } + + return true; +} + +// Close an opened endpoint +bool hcd_edpt_close(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void)rhport; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = tu_edpt_dir(ep_addr); + + // Search in ATL + if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.atl, IP3516_ATL_NUM, sizeof(ip3516_atl_t), NULL)) { + return true; + } + + // Search in INT + if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.intr, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), NULL)) { + return true; + } + + // Search in ISO + if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.iso, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), NULL)) { + return true; + } + + return false; +} + +// Submit a transfer on an endpoint +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { + (void)rhport; + + return edpt_xfer(dev_addr, ep_addr, buffer, buflen, false); +} + +// Abort a queued transfer. Note: it can only abort transfer that has not been started +// Return true if a queued transfer is aborted, false if there is no transfer to abort +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void)rhport; + + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = tu_edpt_dir(ep_addr); + + // Search in ATL + if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.atl, IP3516_ATL_NUM, sizeof(ip3516_atl_t), + &USBHSH->ATLPTDS)) { + return true; + } + + // Search in INT + if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.intr, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), + &USBHSH->INTPTDS)) { + return true; + } + + // Search in ISO + if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.iso, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), + &USBHSH->ISOPTDS)) { + return true; + } + + return false; +} + +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) { + (void)rhport; + + return edpt_xfer(dev_addr, 0x00, (uint8_t *)(uintptr_t)setup_packet, 8, true); +} + +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void)rhport; + + intptr_t ptd_ptr = find_opened_ptd(dev_addr, ep_addr); + TU_ASSERT(ptd_ptr != 0); + + ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state)); + ptd_clear_state(ptd_state); + ptd_state->data_toggle = 0; // reset data toggle to DATA0 + + return true; +} + +//--------------------------------------------------------------------+ +// Interrupt Handler +//--------------------------------------------------------------------+ + +// Handle port status change event +static inline void handle_port_status_change(uint8_t rhport) { + const uint32_t status = USBHSH->PORTSC1; + + if (status & USBHSH_PORTSC1_CSC_MASK) { + if (status & USBHSH_PORTSC1_CCS_MASK && !_hcd_data.attached) { + _hcd_data.attached = true; + hcd_event_device_attach(rhport, true); + } else { + _hcd_data.attached = false; + hcd_event_device_remove(rhport, true); + #if ((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U)) + // disable phy disconnection for high speed + USBPHY->CTRL &= ~USBPHY_CTRL_ENHOSTDISCONDETECT_MASK; + #endif + } + } + + USBHSH->PORTSC1 |= status & USBHSH_PORTSC1_W1C_MASK; +} + +// Handle PTD done interrupt +static inline void handle_ptd_done(uint32_t done_status, intptr_t ptd_array, bool is_async) { + uint8_t max_count = is_async ? IP3516_ATL_NUM : IP3516_PTL_NUM; + uint8_t stride = is_async ? sizeof(ip3516_atl_t) : sizeof(ip3516_ptl_t); + + for (uint8_t i = 0; i < max_count; i++) { + if (done_status & (1 << i)) { + intptr_t ptd_ptr = ptd_array + i * stride; + ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2)); + ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state)); + + xfer_result_t result; + if (ptd_state->halt) { + result = XFER_RESULT_STALLED; + } else if (ptd_state->error || ptd_state->babble) { + result = XFER_RESULT_FAILED; + } else { + result = XFER_RESULT_SUCCESS; + } + + uint8_t ep_addr = ptd_ctrl2->ep_num | (ptd_state->token == IP3516_PTD_TOKEN_IN ? 0x80 : 0x00); + + hcd_event_xfer_complete(ptd_ctrl2->dev_addr, ep_addr, ptd_state->xferred_len, result, true); + } + } +} + +void hcd_int_handler(uint8_t rhport, bool in_isr) { + (void)in_isr; + + uint32_t int_status = USBHSH->USBSTS; + USBHSH->USBSTS = int_status; // clear interrupt status + + // Port Change Detect + if (int_status & USBHSH_USBSTS_PCD_MASK) { + handle_port_status_change(rhport); + } + + // Frame List Rollover + if (int_status & USBHSH_USBSTS_FLR_MASK) { + _hcd_data.uframe_number += _hcd_data.uframe_length; + } + + // ATL done + if (int_status & USBHSH_USBSTS_ATL_IRQ_MASK) { + uint32_t done_status = USBHSH->ATLPTDD; + handle_ptd_done(done_status, (intptr_t)&_ptd.atl, true); + USBHSH->ATLPTDD = done_status; + } + + // INT done + if (int_status & USBHSH_USBSTS_INT_IRQ_MASK) { + uint32_t done_status = USBHSH->INTPTDD; + handle_ptd_done(done_status, (intptr_t)&_ptd.intr, false); + USBHSH->INTPTDD = done_status; + } + + // ISO done + if (int_status & USBHSH_USBSTS_ISO_IRQ_MASK) { + uint32_t done_status = USBHSH->ISOPTDD; + handle_ptd_done(done_status, (intptr_t)&_ptd.iso, false); + USBHSH->ISOPTDD = done_status; + } +} + +#endif diff --git a/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h new file mode 100644 index 000000000..5214c18ae --- /dev/null +++ b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h @@ -0,0 +1,188 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 HiFiPhile (Zixun LI) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_HCD_IP3516_H_ +#define TUSB_HCD_IP3516_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// IP3516 CONFIGURATION & CONSTANTS +//--------------------------------------------------------------------+ + +#define IP3516_ATL_NUM 32 +#define IP3516_PTL_NUM 32 + +#define IP3516_PTD_TOKEN_OUT 0x00U +#define IP3516_PTD_TOKEN_IN 0x01U +#define IP3516_PTD_TOKEN_SETUP 0x02U + +#define IP3516_PTD_EPTYPE_OUT 0x00U +#define IP3516_PTD_EPTYPE_IN 0x01U +#define IP3516_PTD_EPTYPE_SETUP 0x02U + +#define IP3516_PTD_MAX_TRANSFER_LENGTH 0x7FFFU + +#define IP3516_PTD_DATA_ADDR_MASK 0xFFFFU + +#define IP3516_MAX_UFRAME (1UL << 8) + +#define IP3516_PERIODIC_TRANSFER_GAP (3U) +#define IP3516_ISO_MULTIPLE_TRANSFER (8U) + + +//--------------------------------------------------------------------+ +// IP3516 PTD Data Structure +//--------------------------------------------------------------------+ + +// Control Word 1 +typedef union { + uint32_t value; + struct { + uint32_t valid : 1; + uint32_t next_ptd : 5; + uint32_t : 1; + uint32_t jump : 1; + uint32_t uframe : 8; + uint32_t mps : 11; + uint32_t : 1; + uint32_t mult : 2; + uint32_t : 2; + }; +} ptd_ctrl1_t; + +TU_VERIFY_STATIC(sizeof(ptd_ctrl1_t) == 4, "size is not correct"); + +// Control Word 2 +typedef union { + uint32_t value; + struct { + uint32_t ep_num : 4; + uint32_t dev_addr : 7; + uint32_t split : 1; + uint32_t reload : 4; + uint32_t speed : 2; + uint32_t hub_port : 7; + uint32_t hub_addr : 7; + }; +} ptd_ctrl2_t; + +TU_VERIFY_STATIC(sizeof(ptd_ctrl2_t) == 4, "size is not correct"); + +// Data Word +typedef union { + uint32_t value; + struct { + uint32_t xfer_len : 15; + uint32_t intr : 1; + uint32_t data_addr : 16; + }; +} ptd_data_t; + +TU_VERIFY_STATIC(sizeof(ptd_data_t) == 4, "size is not correct"); + +// State Word +typedef union { + uint32_t value; + struct { + uint32_t xferred_len : 15; + uint32_t token : 2; + uint32_t ep_type : 2; + uint32_t nak_cnt : 4; + uint32_t err_cnt : 2; + uint32_t data_toggle : 1; + uint32_t ping : 1; + uint32_t start_complete : 1; + uint32_t error : 1; + uint32_t babble : 1; + uint32_t halt : 1; + uint32_t active : 1; + }; +} ptd_state_t; + +TU_VERIFY_STATIC(sizeof(ptd_state_t) == 4, "size is not correct"); + +// Status Word +typedef union { + uint32_t value; + struct { + uint32_t uframe_active : 8; + uint32_t iso_status0 : 3; + uint32_t iso_status1 : 3; + uint32_t iso_status2 : 3; + uint32_t iso_status3 : 3; + uint32_t iso_status4 : 3; + uint32_t iso_status5 : 3; + uint32_t iso_status6 : 3; + uint32_t iso_status7 : 3; + }; +} ptd_status_t; + +TU_VERIFY_STATIC(sizeof(ptd_status_t) == 4, "size is not correct"); + +// ATL (Asynchronous Transfer List) structure +typedef volatile struct { + ptd_ctrl1_t ctrl1; + ptd_ctrl2_t ctrl2; + ptd_data_t data; + ptd_state_t state; +} ip3516_atl_t; + +TU_VERIFY_STATIC(sizeof(ip3516_atl_t) == 16, "size is not correct"); + +// PTL (Periodic Transfer List) structure +typedef volatile struct { + ptd_ctrl1_t ctrl1; + ptd_ctrl2_t ctrl2; + ptd_data_t data; + ptd_state_t state; + ptd_status_t status; + union { + uint32_t value; + struct { + uint32_t uframe_complete : 8; + uint32_t spl_iso_in_0 : 24; + }; + } iso_in_0; + uint32_t iso_in_1; + uint32_t iso_in_2; +} ip3516_ptl_t; + +TU_VERIFY_STATIC(sizeof(ip3516_ptl_t) == 32, "size is not correct"); + +// Proprietary Transfer Descriptor +typedef struct { + ip3516_ptl_t intr[IP3516_PTL_NUM]; + ip3516_ptl_t iso[IP3516_PTL_NUM]; + ip3516_atl_t atl[IP3516_ATL_NUM]; +} ip3516_ptd_t; + +#ifdef __cplusplus +} +#endif +#endif /* TUSB_HCD_IP3516_H_ */ diff --git a/src/portable/ohci/ohci.c b/src/portable/ohci/ohci.c index 5ca093506..4b248d9e6 100644 --- a/src/portable/ohci/ohci.c +++ b/src/portable/ohci/ohci.c @@ -243,7 +243,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { OHCI_CONTROL_LIST_BULK_ENABLE_MASK | OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK; // TODO Isochronous OHCI_REG->frame_interval = (OHCI_FMINTERVAL_FSMPS << 16) | OHCI_FMINTERVAL_FI; - OHCI_REG->frame_interval ^= (1 << 31); //Must toggle when frame_interval is updated. + OHCI_REG->frame_interval ^= (1ul << 31); //Must toggle when frame_interval is updated. OHCI_REG->periodic_start = (OHCI_FMINTERVAL_FI * 9) / 10; // Periodic start is 90% of frame interval OHCI_REG->control_bit.hc_functional_state = OHCI_CONTROL_FUNCSTATE_OPERATIONAL; // make HC's state to operational state TODO use this to suspend (save power) @@ -557,6 +557,7 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { (void) rhport; ohci_ed_t * const p_ed = ed_from_addr(dev_addr, ep_addr); + TU_ASSERT(p_ed); ohci_ed_word2_t td_head = p_ed->td_head; td_head.toggle = 0; // reset data toggle diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 15852f29f..240e6c727 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -39,122 +39,103 @@ #include "device/dcd.h" // Current implementation force vbus detection as always present, causing device think it is always plugged into host. -// Therefore it cannot detect disconnect event, mistaken it as suspend. +// Therefore, it cannot detect disconnect event, mistaken it as suspend. // Note: won't work if change to 0 (for now) -#define FORCE_VBUS_DETECT 1 + #define FORCE_VBUS_DETECT 1 + + #define USB_INTS_ERROR_BITS \ + (USB_INTS_ERROR_DATA_SEQ_BITS | USB_INTS_ERROR_BIT_STUFF_BITS | USB_INTS_ERROR_CRC_BITS | \ + USB_INTS_ERROR_RX_OVERFLOW_BITS | USB_INTS_ERROR_RX_TIMEOUT_BITS) /*------------------------------------------------------------------*/ /* Low level controller *------------------------------------------------------------------*/ -// Init these in dcd_init -static uint8_t* next_buffer_ptr; +// HW buffer pointer from USB buffer space (max 3840 bytes) +static uint8_t *hw_buffer_ptr; // USB_MAX_ENDPOINTS Endpoints, direction TUSB_DIR_OUT for out and TUSB_DIR_IN for in. static struct hw_endpoint hw_endpoints[USB_MAX_ENDPOINTS][2]; -// SOF may be used by remote wakeup as RESUME, this indicate whether SOF is actually used by usbd +// SOF may be used by remote wakeup as RESUME, this indicates whether SOF is actually used by usbd static bool _sof_enable = false; -TU_ATTR_ALWAYS_INLINE static inline struct hw_endpoint* hw_endpoint_get_by_num(uint8_t num, tusb_dir_t dir) { - return &hw_endpoints[num][dir]; -} - -TU_ATTR_ALWAYS_INLINE static inline struct hw_endpoint* hw_endpoint_get_by_addr(uint8_t ep_addr) { - uint8_t num = tu_edpt_number(ep_addr); - tusb_dir_t dir = tu_edpt_dir(ep_addr); - return hw_endpoint_get_by_num(num, dir); -} - -// Allocate from the USB buffer space (max 3840 bytes) -static void hw_endpoint_alloc(struct hw_endpoint* ep, size_t size) { - // round up size to multiple of 64 - size = tu_round_up(ep->wMaxPacketSize, 64); - - // double buffered Bulk endpoint - if (ep->transfer_type == TUSB_XFER_BULK) { - size *= 2u; - } - - // assign buffer - ep->hw_data_buf = next_buffer_ptr; - next_buffer_ptr += size; - - hard_assert(next_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data)); - pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->hw_data_buf); +TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get(uint8_t epnum, tusb_dir_t dir) { + return &hw_endpoints[epnum][dir]; } -// Enable endpoint -TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_enable(struct hw_endpoint* ep) { - uint32_t const reg = EP_CTRL_ENABLE_BITS | ((uint) ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hw_data_buf); - *ep->endpoint_control = reg; +TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get_by_addr(uint8_t ep_addr) { + const uint8_t num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + return hw_endpoint_get(num, dir); } // main processing for dcd_edpt_iso_activate -static void hw_endpoint_init(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - - const uint8_t num = tu_edpt_number(ep_addr); - const tusb_dir_t dir = tu_edpt_dir(ep_addr); - - ep->ep_addr = ep_addr; - - // For device, IN is a tx transfer and OUT is an rx transfer - ep->rx = (dir == TUSB_DIR_OUT); - - ep->next_pid = 0u; +static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { + ep->ep_addr = ep_addr; + ep->next_pid = 0u; ep->wMaxPacketSize = wMaxPacketSize; - ep->transfer_type = transfer_type; - - // Every endpoint has a buffer control register in dpram - if (dir == TUSB_DIR_IN) { - ep->buffer_control = &usb_dpram->ep_buf_ctrl[num].in; - } else { - ep->buffer_control = &usb_dpram->ep_buf_ctrl[num].out; - } // Clear existing buffer control state - *ep->buffer_control = 0; - - if (num == 0) { - // EP0 has no endpoint control register because the buffer offsets are fixed - ep->endpoint_control = NULL; + io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); + *buf_ctrl_reg = 0; + // allocated hw buffer + const uint8_t epnum = tu_edpt_number(ep_addr); + if (epnum == 0) { // Buffer offset is fixed (also double buffered) ep->hw_data_buf = (uint8_t*) &usb_dpram->ep0_buf_a[0]; } else { - // Set the endpoint control register (starts at EP1, hence num-1) - if (dir == TUSB_DIR_IN) { - ep->endpoint_control = &usb_dpram->ep_ctrl[num - 1].in; - } else { - ep->endpoint_control = &usb_dpram->ep_ctrl[num - 1].out; + // round up size to multiple of 64 + uint16_t size = (uint16_t)tu_round_up(wMaxPacketSize, 64); + + // double buffered Bulk endpoint + if (transfer_type == TUSB_XFER_BULK) { + size *= 2u; + + #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { + ep->e15_bulk_in = true; + } + #endif } + + // assign buffer + ep->hw_data_buf = hw_buffer_ptr; + hw_buffer_ptr += size; + + hard_assert(hw_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data)); + pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->hw_data_buf); } } -// Init, allocate buffer and enable endpoint -static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - hw_endpoint_init(ep_addr, wMaxPacketSize, transfer_type); - const uint8_t num = tu_edpt_number(ep_addr); - if (num != 0) { - // EP0 is already enabled - hw_endpoint_alloc(ep, ep->wMaxPacketSize); - hw_endpoint_enable(ep); +static void hw_endpoint_enable(hw_endpoint_t *ep, uint8_t transfer_type) { + io_rw_32 *ctrl_reg = hwep_ctrl_reg_device(ep); + // Set endpoint control register to enable (EP0 has no endpoint control register) + if (ctrl_reg != NULL) { + const uint32_t ctrl_value = + EP_CTRL_ENABLE_BITS | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hw_data_buf); + *ctrl_reg = ctrl_value; } } -static void hw_endpoint_xfer(uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) { - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - hw_endpoint_xfer_start(ep, buffer, total_bytes); +// Init and enable endpoint +static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { + const uint8_t epnum = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); + + hw_endpoint_init(ep, ep_addr, wMaxPacketSize, transfer_type); + hw_endpoint_enable(ep, transfer_type); } static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { // Abort any pending transfer + const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); + const uint8_t epnum = tu_edpt_number(ep->ep_addr); + const uint32_t abort_mask = TU_BIT((epnum << 1) | (dir ? 0 : 1)); + // Due to Errata RP2040-E2: ABORT flag is only applicable for B2 and later (unusable for B0, B1). // Which means we are not guaranteed to safely abort pending transfer on B0 and B1. - const uint8_t dir = tu_edpt_dir(ep->ep_addr); - const uint8_t epnum = tu_edpt_number(ep->ep_addr); - const uint32_t abort_mask = TU_BIT((epnum << 1) | (dir ? 0 : 1)); if (rp2040_chip_version() >= 2) { usb_hw_set->abort = abort_mask; while ((usb_hw->abort_done & abort_mask) != abort_mask) {} @@ -165,7 +146,8 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { buf_ctrl |= USB_BUF_CTRL_DATA1_PID; } - _hw_endpoint_buffer_control_set_value32(ep, buf_ctrl); + io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); + hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl); hw_endpoint_reset_transfer(ep); if (rp2040_chip_version() >= 2) { @@ -174,7 +156,7 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { } } -static void __tusb_irq_path_func(hw_handle_buff_status)(void) { +static void __tusb_irq_path_func(handle_hw_buff_status)(void) { uint32_t remaining_buffers = usb_hw->buf_status; pico_trace("buf_status = 0x%08lx\r\n", remaining_buffers); uint bit = 1u; @@ -184,12 +166,13 @@ static void __tusb_irq_path_func(hw_handle_buff_status)(void) { usb_hw_clear->buf_status = bit; // IN transfer for even i, OUT transfer for odd i - struct hw_endpoint* ep = hw_endpoint_get_by_num(i >> 1u, (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN); + const uint8_t epnum = i >> 1u; + const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN; + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); - // Continue xfer - bool done = hw_endpoint_xfer_continue(ep); + const bool done = hw_endpoint_xfer_continue(ep); if (done) { - // Notify + // Notify usbd const uint16_t xferred_len = ep->xferred_len; hw_endpoint_reset_transfer(ep); dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true); @@ -204,7 +187,7 @@ TU_ATTR_ALWAYS_INLINE static inline void reset_ep0(void) { // If we have finished this transfer on EP0 set pid back to 1 for next // setup transfer. Also clear a stall in case for (uint8_t dir = 0; dir < 2; dir++) { - struct hw_endpoint* ep = hw_endpoint_get_by_num(0, dir); + struct hw_endpoint *ep = hw_endpoint_get(0, dir); ep->next_pid = 1u; if (ep->active) { hw_endpoint_abort_xfer(ep); // Abort any pending transfer per USB specs @@ -223,11 +206,11 @@ static void __tusb_irq_path_func(reset_non_control_endpoints)(void) { tu_memclr(hw_endpoints[1], sizeof(hw_endpoints) - 2 * sizeof(hw_endpoint_t)); // reclaim buffer space - next_buffer_ptr = &usb_dpram->epx_data[0]; + hw_buffer_ptr = &usb_dpram->epx_data[0]; } static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { - uint32_t const status = usb_hw->ints; + const uint32_t status = usb_hw->ints; uint32_t handled = 0; if (status & USB_INTF_DEV_SOF_BITS) { @@ -240,27 +223,26 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { e15_last_sof = time_us_32(); for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) { - struct hw_endpoint* ep = hw_endpoint_get_by_num(i, TUSB_DIR_IN); + struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN); // Active Bulk IN endpoint requires SOF - if ((ep->transfer_type == TUSB_XFER_BULK) && ep->active) { + if (ep->e15_bulk_in && ep->active) { keep_sof_alive = true; hw_endpoint_lock_update(ep, 1); - - // Deferred enable? if (ep->pending) { ep->pending = 0; hw_endpoint_start_next_buffer(ep); } - hw_endpoint_lock_update(ep, -1); } } #endif // disable SOF interrupt if it is used for RESUME in remote wakeup - if (!keep_sof_alive && !_sof_enable) usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS; + if (!keep_sof_alive && !_sof_enable) { + usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS; + } dcd_event_sof(0, usb_hw->sof_rd & USB_SOF_RD_BITS, true); } @@ -269,7 +251,7 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { // before closing the EP, the events will be delivered in same order. if (status & USB_INTS_BUFF_STATUS_BITS) { handled |= USB_INTS_BUFF_STATUS_BITS; - hw_handle_buff_status(); + handle_hw_buff_status(); } if (status & USB_INTS_SETUP_REQ_BITS) { @@ -287,15 +269,12 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { #if FORCE_VBUS_DETECT == 0 // Since we force VBUS detect On, device will always think it is connected and // couldn't distinguish between disconnect and suspend - if (status & USB_INTS_DEV_CONN_DIS_BITS) - { + if (status & USB_INTS_DEV_CONN_DIS_BITS) { handled |= USB_INTS_DEV_CONN_DIS_BITS; - if ( usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS ) - { + if (usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS) { // Connected: nothing to do - }else - { + } else { // Disconnected dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true); } @@ -315,10 +294,12 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { dcd_event_bus_reset(0, TUSB_SPEED_FULL, true); usb_hw_clear->sie_status = USB_SIE_STATUS_BUS_RESET_BITS; -#if TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX + #if TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX // Only run enumeration workaround if pull up is enabled - if (usb_hw->sie_ctrl & USB_SIE_CTRL_PULLUP_EN_BITS) rp2040_usb_device_enumeration_fix(); -#endif + if (usb_hw->sie_ctrl & USB_SIE_CTRL_PULLUP_EN_BITS) { + rp2040_usb_device_enumeration_fix(); + } + #endif } /* Note from pico datasheet 4.1.2.6.4 (v1.2) @@ -346,13 +327,6 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { } } -#define USB_INTS_ERROR_BITS ( \ - USB_INTS_ERROR_DATA_SEQ_BITS | \ - USB_INTS_ERROR_BIT_STUFF_BITS | \ - USB_INTS_ERROR_CRC_BITS | \ - USB_INTS_ERROR_RX_OVERFLOW_BITS | \ - USB_INTS_ERROR_RX_TIMEOUT_BITS) - /*------------------------------------------------------------------*/ /* Controller API *------------------------------------------------------------------*/ @@ -369,9 +343,9 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { TU_LOG(2, "Chip Version B%u\r\n", rp2040_chip_version()); // Reset hardware to default state - rp2040_usb_init(); + rp2usb_init(); -#if FORCE_VBUS_DETECT + #if FORCE_VBUS_DETECT // Force VBUS detect so the device thinks it is plugged into a host usb_hw->pwr = USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS; #endif @@ -425,12 +399,11 @@ void dcd_int_disable(__unused uint8_t rhport) { irq_set_enabled(USBCTRL_IRQ, false); } -void dcd_set_address(__unused uint8_t rhport, __unused uint8_t dev_addr) { - assert(rhport == 0); - +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + (void)dev_addr; // Can't set device address in hardware until status xfer has complete // Send 0len complete response on EP0 IN - hw_endpoint_xfer(0x80, NULL, 0); + dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); } void dcd_remote_wakeup(__unused uint8_t rhport) { @@ -475,7 +448,6 @@ void dcd_sof_enable(uint8_t rhport, bool en) { /*------------------------------------------------------------------*/ /* DCD Endpoint port *------------------------------------------------------------------*/ - void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) { (void) rhport; @@ -489,7 +461,6 @@ void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* req bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { (void) rhport; const uint8_t xfer_type = desc_edpt->bmAttributes.xfer; - TU_VERIFY(xfer_type != TUSB_XFER_ISOCHRONOUS); hw_endpoint_open(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), xfer_type); return true; } @@ -497,56 +468,65 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { // New API: Allocate packet buffer used by ISO endpoints // Some MCU need manual packet buffer allocation, we allocate the largest size to avoid clustering bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { - (void) rhport; - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - hw_endpoint_init(ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS); - hw_endpoint_alloc(ep, largest_packet_size); + (void)rhport; + struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); + hw_endpoint_init(ep, ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS); return true; } // New API: Configure and enable an ISO endpoint according to descriptor -bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) { - (void) rhport; - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_desc->bEndpointAddress); +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) { + (void)rhport; + const uint8_t epnum = tu_edpt_number(ep_desc->bEndpointAddress); + const tusb_dir_t dir = tu_edpt_dir(ep_desc->bEndpointAddress); + struct hw_endpoint *ep = hw_endpoint_get(epnum, dir); TU_ASSERT(ep->hw_data_buf != NULL); // must be inited and allocated previously if (ep->active) { hw_endpoint_abort_xfer(ep); // abort any pending transfer } - ep->wMaxPacketSize = ep_desc->wMaxPacketSize; - hw_endpoint_enable(ep); + + hw_endpoint_enable(ep, TUSB_XFER_ISOCHRONOUS); return true; } void dcd_edpt_close_all(uint8_t rhport) { (void) rhport; - // may need to use EP Abort reset_non_control_endpoints(); } -bool dcd_edpt_xfer(__unused uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, bool is_isr) { - (void) is_isr; - assert(rhport == 0); - hw_endpoint_xfer(ep_addr, buffer, total_bytes); +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { + (void)rhport; + (void)is_isr; + hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr); + hw_endpoint_xfer_start(ep, buffer, NULL, total_bytes); + return true; +} + +bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t total_bytes, bool is_isr) { + (void)rhport; + (void)is_isr; + hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr); + hw_endpoint_xfer_start(ep, NULL, ff, total_bytes); return true; } void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; + (void)rhport; + const uint8_t epnum = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); - if (tu_edpt_number(ep_addr) == 0) { + if (epnum == 0) { // A stall on EP0 has to be armed so it can be cleared on the next setup packet - usb_hw_set->ep_stall_arm = (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS - : USB_EP_STALL_ARM_EP0_OUT_BITS; + usb_hw_set->ep_stall_arm = (dir == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS; } - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - - // stall and clear current pending buffer - // may need to use EP_ABORT - _hw_endpoint_buffer_control_set_value32(ep, USB_BUF_CTRL_STALL); + // stall and clear current pending buffer, may need to use EP_ABORT + io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); + hwbuf_ctrl_set(buf_ctrl_reg, USB_BUF_CTRL_STALL); } void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { @@ -557,7 +537,8 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { // clear stall also reset toggle to DATA0, ready for next transfer ep->next_pid = 0; - _hw_endpoint_buffer_control_clear_mask32(ep, USB_BUF_CTRL_STALL); + io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); + hwbuf_ctrl_clear_mask(buf_ctrl_reg, USB_BUF_CTRL_STALL); } } diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index cd8c905d5..06c0ce340 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -62,34 +62,33 @@ enum { USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS }; -static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) -{ +static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) { uint8_t num = tu_edpt_number(ep_addr); - if ( num == 0 ) return &epx; + if (num == 0) { + return &epx; + } - for ( uint32_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++ ) - { + for (uint32_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { struct hw_endpoint *ep = &ep_pool[i]; - if ( ep->configured && (ep->dev_addr == dev_addr) && (ep->ep_addr == ep_addr) ) return ep; + if (ep->configured && (ep->dev_addr == dev_addr) && (ep->ep_addr == ep_addr)) { + return ep; + } } return NULL; } -TU_ATTR_ALWAYS_INLINE static inline uint8_t dev_speed(void) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t dev_speed(void) { return (usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS) >> USB_SIE_STATUS_SPEED_LSB; } -TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) -{ +TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { // If this device is different to the speed of the root device // (i.e. is a low speed device on a full speed hub) then need pre return hcd_port_speed_get(0) != tuh_speed_get(dev_addr); } -static void __tusb_irq_path_func(hw_xfer_complete)(struct hw_endpoint *ep, xfer_result_t xfer_result) -{ +static void __tusb_irq_path_func(hw_xfer_complete)(struct hw_endpoint *ep, xfer_result_t xfer_result) { // Mark transfer as done before we tell the tinyusb stack uint8_t dev_addr = ep->dev_addr; uint8_t ep_addr = ep->ep_addr; @@ -98,47 +97,28 @@ static void __tusb_irq_path_func(hw_xfer_complete)(struct hw_endpoint *ep, xfer_ hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true); } -static void __tusb_irq_path_func(_handle_buff_status_bit)(uint bit, struct hw_endpoint *ep) -{ +static void __tusb_irq_path_func(handle_hwbuf_status_bit)(uint bit, struct hw_endpoint *ep) { usb_hw_clear->buf_status = bit; - // EP may have been stalled? - assert(ep->active); - bool done = hw_endpoint_xfer_continue(ep); - if ( done ) - { + const bool done = hw_endpoint_xfer_continue(ep); + if (done) { hw_xfer_complete(ep, XFER_RESULT_SUCCESS); } } -static void __tusb_irq_path_func(hw_handle_buff_status)(void) -{ - uint32_t remaining_buffers = usb_hw->buf_status; - pico_trace("buf_status 0x%08lx\n", remaining_buffers); +static void __tusb_irq_path_func(handle_hwbuf_status)(void) { + uint32_t buf_status = usb_hw->buf_status; + pico_trace("buf_status 0x%08lx\n", buf_status); // Check EPX first - uint bit = 0b1; - if ( remaining_buffers & bit ) - { - remaining_buffers &= ~bit; + uint32_t bit = 1u; + if (buf_status & bit) { + buf_status &= ~bit; struct hw_endpoint * ep = &epx; - - uint32_t ep_ctrl = *ep->endpoint_control; - if ( ep_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS ) - { - TU_LOG(3, "Double Buffered: "); - } - else - { - TU_LOG(3, "Single Buffered: "); - } - TU_LOG_HEX(3, ep_ctrl); - - _handle_buff_status_bit(bit, ep); + handle_hwbuf_status_bit(bit, ep); } // Check "interrupt" (asynchronous) endpoints for both IN and OUT - for ( uint i = 1; i <= USB_HOST_INTERRUPT_ENDPOINTS && remaining_buffers; i++ ) - { + for (uint i = 1; i <= USB_HOST_INTERRUPT_ENDPOINTS && buf_status; i++) { // EPX is bit 0 & 1 // IEP1 IN is bit 2 // IEP1 OUT is bit 3 @@ -147,20 +127,17 @@ static void __tusb_irq_path_func(hw_handle_buff_status)(void) // IEP3 IN is bit 6 // IEP3 OUT is bit 7 // etc - for ( uint j = 0; j < 2; j++ ) - { + for (uint j = 0; j < 2; j++) { bit = 1 << (i * 2 + j); - if ( remaining_buffers & bit ) - { - remaining_buffers &= ~bit; - _handle_buff_status_bit(bit, &ep_pool[i]); + if (buf_status & bit) { + buf_status &= ~bit; + handle_hwbuf_status_bit(bit, &ep_pool[i]); } } } - if ( remaining_buffers ) - { - panic("Unhandled buffer %d\n", remaining_buffers); + if (buf_status) { + panic("Unhandled buffer %d\n", buf_status); } } @@ -220,7 +197,7 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { handled |= USB_INTS_BUFF_STATUS_BITS; TU_LOG(2, "Buffer complete\r\n"); - hw_handle_buff_status(); + handle_hwbuf_status(); } if ( status & USB_INTS_TRANS_COMPLETE_BITS ) @@ -240,9 +217,8 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) if ( status & USB_INTS_ERROR_DATA_SEQ_BITS ) { usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS; - TU_LOG(3, " Seq Error: [0] = 0x%04u [1] = 0x%04x\r\n", - tu_u32_low16(*epx.buffer_control), - tu_u32_high16(*epx.buffer_control)); + TU_LOG(3, " Seq Error: [0] = 0x%04u [1] = 0x%04x\r\n", tu_u32_low16(*hwbuf_ctrl_reg_host(&epx)), + tu_u32_high16(*hwbuf_ctrl_reg_host(&epx))); panic("Data Seq Error \n"); } @@ -266,7 +242,7 @@ static struct hw_endpoint *_next_free_interrupt_ep(void) ep = &ep_pool[i]; if ( !ep->configured ) { - // Will be configured by _hw_endpoint_init / _hw_endpoint_allocate + // Will be configured by hw_endpoint_init / hw_endpoint_allocate ep->interrupt_num = (uint8_t) (i - 1); return ep; } @@ -274,41 +250,31 @@ static struct hw_endpoint *_next_free_interrupt_ep(void) return ep; } -static struct hw_endpoint *_hw_endpoint_allocate(uint8_t transfer_type) -{ - struct hw_endpoint * ep = NULL; +static hw_endpoint_t *hw_endpoint_allocate(uint8_t transfer_type) { + hw_endpoint_t *ep = NULL; - if ( transfer_type != TUSB_XFER_CONTROL ) - { + if (transfer_type == TUSB_XFER_CONTROL) { + ep = &epx; + ep->hw_data_buf = &usbh_dpram->epx_data[0]; + } else { // Note: even though datasheet name these "Interrupt" endpoints. These are actually // "Asynchronous" endpoints and can be used for other type such as: Bulk (ISO need confirmation) ep = _next_free_interrupt_ep(); pico_info("Allocate %s ep %d\n", tu_edpt_type_str(transfer_type), ep->interrupt_num); assert(ep); - ep->buffer_control = &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl; - ep->endpoint_control = &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl; // 0 for epx (double buffered): TODO increase to 1024 for ISO // 2x64 for intep0 // 3x64 for intep1 // etc ep->hw_data_buf = &usbh_dpram->epx_data[64 * (ep->interrupt_num + 2)]; } - else - { - ep = &epx; - ep->buffer_control = &usbh_dpram->epx_buf_ctrl; - ep->endpoint_control = &usbh_dpram->epx_ctrl; - ep->hw_data_buf = &usbh_dpram->epx_data[0]; - } return ep; } -static void _hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type, uint8_t bmInterval) -{ +static void hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep_addr, uint16_t wMaxPacketSize, + uint8_t transfer_type, uint8_t bmInterval) { // Already has data buffer, endpoint control, and buffer control allocated at this point - assert(ep->endpoint_control); - assert(ep->buffer_control); assert(ep->hw_data_buf); uint8_t const num = tu_edpt_number(ep_addr); @@ -317,49 +283,41 @@ static void _hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep->ep_addr = ep_addr; ep->dev_addr = dev_addr; - // For host, IN to host == RX, anything else rx == false - ep->rx = (dir == TUSB_DIR_IN); - // Response to a setup packet on EP0 starts with pid of 1 ep->next_pid = (num == 0 ? 1u : 0u); ep->wMaxPacketSize = wMaxPacketSize; - ep->transfer_type = transfer_type; - pico_trace("hw_endpoint_init dev %d ep %02X xfer %d\n", ep->dev_addr, ep->ep_addr, ep->transfer_type); + pico_trace("hw_endpoint_init dev %d ep %02X xfer %d\n", ep->dev_addr, ep->ep_addr, transfer_type); pico_trace("dev %d ep %02X setup buffer @ 0x%p\n", ep->dev_addr, ep->ep_addr, ep->hw_data_buf); uint dpram_offset = hw_data_offset(ep->hw_data_buf); // Bits 0-5 should be 0 assert(!(dpram_offset & 0b111111)); // Fill in endpoint control register with buffer offset - uint32_t ep_reg = EP_CTRL_ENABLE_BITS - | EP_CTRL_INTERRUPT_PER_BUFFER - | (ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) - | dpram_offset; - if ( bmInterval ) - { - ep_reg |= (uint32_t) ((bmInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); + uint32_t ctrl_value = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | + ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset; + if (bmInterval) { + ctrl_value |= (uint32_t)((bmInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); } - *ep->endpoint_control = ep_reg; - pico_trace("endpoint control (0x%p) <- 0x%lx\n", ep->endpoint_control, ep_reg); + + io_rw_32 *ctrl_reg = hwep_ctrl_reg_host(ep); + *ctrl_reg = ctrl_value; + pico_trace("endpoint control (0x%p) <- 0x%lx\n", ctrl_reg, ctrl_value); ep->configured = true; - if ( ep != &epx ) - { + if (ep != &epx) { // Endpoint has its own addr_endp and interrupt bits to be setup! // This is an interrupt/async endpoint. so need to set up ADDR_ENDP register with: // - device address // - endpoint number / direction // - preamble - uint32_t reg = (uint32_t) (dev_addr | (num << USB_ADDR_ENDP1_ENDPOINT_LSB)); + uint32_t reg = (uint32_t)(dev_addr | (num << USB_ADDR_ENDP1_ENDPOINT_LSB)); - if ( dir == TUSB_DIR_OUT ) - { + if (dir == TUSB_DIR_OUT) { reg |= USB_ADDR_ENDP1_INTEP_DIR_BITS; } - if ( need_pre(dev_addr) ) - { + if (need_pre(dev_addr)) { reg |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS; } usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = reg; @@ -367,8 +325,7 @@ static void _hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t // Finally, enable interrupt that endpoint usb_hw_set->int_ep_ctrl = 1 << (ep->interrupt_num + 1); - // If it's an interrupt endpoint we need to set up the buffer control - // register + // If it's an interrupt endpoint we need to set up the buffer control register } } @@ -382,7 +339,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { assert(rhport == 0); // Reset any previous state - rp2040_usb_init(); + rp2usb_init(); // Force VBUS detect to always present, for now we assume vbus is always provided (without using VBUS En) usb_hw->pwr = USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS; @@ -466,8 +423,8 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { // reset epx if it is currently active with unplugged device if (epx.configured && epx.active && epx.dev_addr == dev_addr) { epx.configured = false; - *epx.endpoint_control = 0; - *epx.buffer_control = 0; + *hwep_ctrl_reg_host(&epx) = 0; + *hwbuf_ctrl_reg_host(&epx) = 0; hw_endpoint_reset_transfer(&epx); } @@ -482,54 +439,41 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { // unconfigure the endpoint ep->configured = false; - *ep->endpoint_control = 0; - *ep->buffer_control = 0; + *hwep_ctrl_reg_host(ep) = 0; + *hwbuf_ctrl_reg_host(ep) = 0; hw_endpoint_reset_transfer(ep); } } } } -uint32_t hcd_frame_number(uint8_t rhport) -{ - (void) rhport; +uint32_t hcd_frame_number(uint8_t rhport) { + (void)rhport; return usb_hw->sof_rd; } -void hcd_int_enable(uint8_t rhport) -{ - (void) rhport; - assert(rhport == 0); +void hcd_int_enable(uint8_t rhport) { + (void)rhport; irq_set_enabled(USBCTRL_IRQ, true); } -void hcd_int_disable(uint8_t rhport) -{ - (void) rhport; +void hcd_int_disable(uint8_t rhport) { + (void)rhport; // todo we should check this is disabling from the correct core; note currently this is never called - assert(rhport == 0); irq_set_enabled(USBCTRL_IRQ, false); } //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ -bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) -{ - (void) rhport; - +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { + (void)rhport; pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress); - - // Allocated differently based on if it's an interrupt endpoint or not - struct hw_endpoint *ep = _hw_endpoint_allocate(ep_desc->bmAttributes.xfer); + hw_endpoint_t *ep = hw_endpoint_allocate(ep_desc->bmAttributes.xfer); TU_ASSERT(ep); - _hw_endpoint_init(ep, - dev_addr, - ep_desc->bEndpointAddress, - tu_edpt_packet_size(ep_desc), - ep_desc->bmAttributes.xfer, - ep_desc->bInterval); + hw_endpoint_init(ep, dev_addr, ep_desc->bEndpointAddress, tu_edpt_packet_size(ep_desc), ep_desc->bmAttributes.xfer, + ep_desc->bInterval); return true; } @@ -539,13 +483,12 @@ bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { return false; // TODO not implemented yet } -bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) -{ +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { (void) rhport; pico_trace("hcd_edpt_xfer dev_addr %d, ep_addr 0x%x, len %d\n", dev_addr, ep_addr, buflen); - uint8_t const ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_num = tu_edpt_number(ep_addr); tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr); // Get appropriate ep. Either EPX or interrupt endpoint @@ -557,20 +500,18 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * assert(!ep->active); // Control endpoint can change direction 0x00 <-> 0x80 - if ( ep_addr != ep->ep_addr ) - { + if (ep_addr != ep->ep_addr) { assert(ep_num == 0); // Direction has flipped on endpoint control so re init it but with same properties - _hw_endpoint_init(ep, dev_addr, ep_addr, ep->wMaxPacketSize, ep->transfer_type, 0); + hw_endpoint_init(ep, dev_addr, ep_addr, ep->wMaxPacketSize, TUSB_XFER_CONTROL, 0); } // If a normal transfer (non-interrupt) then initiate using // sie ctrl registers. Otherwise, interrupt ep registers should // already be configured - if ( ep == &epx ) - { - hw_endpoint_xfer_start(ep, buffer, buflen); + if (ep == &epx) { + hw_endpoint_xfer_start(ep, buffer, NULL, buflen); // That has set up buffer control, endpoint control etc // for host we have to initiate the transfer @@ -585,9 +526,8 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS; busy_wait_at_least_cycles(12); usb_hw->sie_ctrl = flags; - }else - { - hw_endpoint_xfer_start(ep, buffer, buflen); + } else { + hw_endpoint_xfer_start(ep, buffer, NULL, buflen); } return true; @@ -611,14 +551,14 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet } // Configure EP0 struct with setup info for the trans complete - struct hw_endpoint * ep = _hw_endpoint_allocate( (uint8_t) TUSB_XFER_CONTROL); + hw_endpoint_t *ep = hw_endpoint_allocate((uint8_t)TUSB_XFER_CONTROL); TU_ASSERT(ep); // EPX should be inactive assert(!ep->active); // EP0 out - _hw_endpoint_init(ep, dev_addr, 0x00, ep->wMaxPacketSize, 0, 0); + hw_endpoint_init(ep, dev_addr, 0x00, ep->wMaxPacketSize, 0, 0); assert(ep->configured); ep->remaining_len = 8; diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 9d0bd762d..3b65f57a4 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -35,34 +35,35 @@ //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF PROTOTYPE //--------------------------------------------------------------------+ -static void _hw_endpoint_xfer_sync(struct hw_endpoint* ep); +static void sync_xfer(hw_endpoint_t *ep); -#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX - static bool e15_is_bulkin_ep(struct hw_endpoint* ep); - static bool e15_is_critical_frame_period(struct hw_endpoint* ep); -#else - #define e15_is_bulkin_ep(x) (false) - #define e15_is_critical_frame_period(x) (false) -#endif - -// if usb hardware is in host mode -TU_ATTR_ALWAYS_INLINE static inline bool is_host_mode(void) { - return (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) ? true : false; -} + #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX +static bool e15_is_critical_frame_period(struct hw_endpoint *ep); + #else + #define e15_is_critical_frame_period(x) (false) + #endif //--------------------------------------------------------------------+ // Implementation //--------------------------------------------------------------------+ // Provide own byte by byte memcpy as not all copies are aligned -static void unaligned_memcpy(void *dst, const void *src, size_t n) { - uint8_t *dst_byte = (uint8_t*)dst; - const uint8_t *src_byte = (const uint8_t*)src; +static void unaligned_memcpy(uint8_t *dst, const uint8_t *src, size_t n) { while (n--) { - *dst_byte++ = *src_byte++; + *dst++ = *src++; } } -void rp2040_usb_init(void) { +void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) { + (void)access_mode; + unaligned_memcpy((uint8_t *)(uintptr_t)hwfifo, src, len); +} + +void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, const tu_hwfifo_access_t *access_mode) { + (void)access_mode; + unaligned_memcpy(dest, (const uint8_t *)(uintptr_t)hwfifo, len); +} + +void rp2usb_init(void) { // Reset usb controller reset_block(RESETS_RESET_USBCTRL_BITS); unreset_block_wait(RESETS_RESET_USBCTRL_BITS); @@ -93,35 +94,38 @@ void __tusb_irq_path_func(hw_endpoint_reset_transfer)(struct hw_endpoint* ep) { ep->user_buf = 0; } -void __tusb_irq_path_func(_hw_endpoint_buffer_control_update32)(struct hw_endpoint* ep, uint32_t and_mask, - uint32_t or_mask) { - uint32_t value = 0; +void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask) { + const bool is_host = rp2usb_is_host_mode(); + uint32_t value = 0; + uint32_t buf_ctrl = *buf_ctrl_reg; if (and_mask) { - value = *ep->buffer_control & and_mask; + value = buf_ctrl & and_mask; } if (or_mask) { value |= or_mask; if (or_mask & USB_BUF_CTRL_AVAIL) { - if (*ep->buffer_control & USB_BUF_CTRL_AVAIL) { - panic("ep %02X was already available", ep->ep_addr); + if (buf_ctrl & USB_BUF_CTRL_AVAIL) { + panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_ctrl_reg); } - *ep->buffer_control = value & ~USB_BUF_CTRL_AVAIL; - // 4.1.2.5.1 Con-current access: 12 cycles (should be good for 48*12Mhz = 576Mhz) after write to buffer control + *buf_ctrl_reg = value & ~USB_BUF_CTRL_AVAIL; + + // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control, we need to + // wait at least 1/48 mhz (usb clock), 12 cycles should be good for 48*12Mhz = 576Mhz. // Don't need delay in host mode as host is in charge - if (!is_host_mode()) { + if (!is_host) { busy_wait_at_least_cycles(12); } } } - *ep->buffer_control = value; + *buf_ctrl_reg = value; } -// prepare buffer, return buffer control -static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint* ep, uint8_t buf_id) { - uint16_t const buflen = tu_min16(ep->remaining_len, ep->wMaxPacketSize); +// prepare buffer, move data if tx, return buffer control +static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx) { + const uint16_t buflen = tu_min16(ep->remaining_len, ep->wMaxPacketSize); ep->remaining_len = (uint16_t) (ep->remaining_len - buflen); uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; @@ -130,10 +134,18 @@ static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint* ep, buf_ctrl |= ep->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID; ep->next_pid ^= 1u; - if (!ep->rx) { - // Copy data from user buffer to hw buffer - unaligned_memcpy(ep->hw_data_buf + buf_id * 64, ep->user_buf, buflen); - ep->user_buf += buflen; + if (!is_rx) { + if (buflen) { + // Copy data from user buffer/fifo to hw buffer + uint8_t *hw_buf = ep->hw_data_buf + buf_id * 64; + if (ep->is_xfer_fifo) { + // not in sram, may mess up timing with E15 workaround + tu_hwfifo_write_from_fifo(hw_buf, ep->user_fifo, buflen, NULL); + } else { + unaligned_memcpy(hw_buf, ep->user_buf, buflen); + ep->user_buf += buflen; + } + } // Mark as full buf_ctrl |= USB_BUF_CTRL_FULL; @@ -146,52 +158,75 @@ static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint* ep, buf_ctrl |= USB_BUF_CTRL_LAST; } - if (buf_id) buf_ctrl = buf_ctrl << 16; + if (buf_id) { + buf_ctrl = buf_ctrl << 16; + } return buf_ctrl; } // Prepare buffer control register value void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep) { - uint32_t ep_ctrl = *ep->endpoint_control; + const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr); + bool is_rx; + bool is_host = false; + io_rw_32 *ep_ctrl_reg; + io_rw_32 *buf_ctrl_reg; + + #if CFG_TUH_ENABLED + is_host = rp2usb_is_host_mode(); + if (is_host) { + buf_ctrl_reg = hwbuf_ctrl_reg_host(ep); + ep_ctrl_reg = hwep_ctrl_reg_host(ep); + is_rx = (dir == TUSB_DIR_IN); + } else + #endif + { + buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); + ep_ctrl_reg = hwep_ctrl_reg_device(ep); + is_rx = (dir == TUSB_DIR_OUT); + } // always compute and start with buffer 0 - uint32_t buf_ctrl = prepare_ep_buffer(ep, 0) | USB_BUF_CTRL_SEL; + uint32_t buf_ctrl = prepare_ep_buffer(ep, 0, is_rx) | USB_BUF_CTRL_SEL; - // For now: skip double buffered for OUT endpoint in Device mode, since - // host could send < 64 bytes and cause short packet on buffer0 - // NOTE: this could happen to Host mode IN endpoint - // Also, Host mode "interrupt" endpoint hardware is only single buffered, - // NOTE2: Currently Host bulk is implemented using "interrupt" endpoint - bool const is_host = is_host_mode(); - bool const force_single = (!is_host && !tu_edpt_dir(ep->ep_addr)) || - (is_host && tu_edpt_number(ep->ep_addr) != 0); + // EP0 has no endpoint control register, also usbd only schedule 1 packet at a time (single buffer) + if (ep_ctrl_reg != NULL) { + uint32_t ep_ctrl = *ep_ctrl_reg; - if (ep->remaining_len && !force_single) { - // Use buffer 1 (double buffered) if there is still data - // TODO: Isochronous for buffer1 bit-field is different than CBI (control bulk, interrupt) + // For now: skip double buffered for RX e.g OUT endpoint in Device mode, since host could send < 64 bytes and cause + // short packet on buffer0 + // NOTE: this could happen to Host mode IN endpoint Also, Host mode "interrupt" endpoint hardware is only single + // buffered, + // NOTE2: Currently Host bulk is implemented using "interrupt" endpoint + const bool force_single = (!is_host && is_rx) || (is_host && tu_edpt_number(ep->ep_addr) != 0); - buf_ctrl |= prepare_ep_buffer(ep, 1); + if (ep->remaining_len && !force_single) { + // Use buffer 1 (double buffered) if there is still data + // TODO: Isochronous for buffer1 bit-field is different than CBI (control bulk, interrupt) - // Set endpoint control double buffered bit if needed - ep_ctrl &= ~EP_CTRL_INTERRUPT_PER_BUFFER; - ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER; - } else { - // Single buffered since 1 is enough - ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER); - ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER; - } + buf_ctrl |= prepare_ep_buffer(ep, 1, is_rx); - *ep->endpoint_control = ep_ctrl; + // Set endpoint control double buffered bit if needed + ep_ctrl &= ~EP_CTRL_INTERRUPT_PER_BUFFER; + ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER; + } else { + // Single buffered since 1 is enough + ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER); + ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER; + } + + *ep_ctrl_reg = ep_ctrl; + } TU_LOG(3, " Prepare BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl)); // Finally, write to buffer_control which will trigger the transfer // the next time the controller polls this dpram address - _hw_endpoint_buffer_control_set_value32(ep, buf_ctrl); + hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl); } -void hw_endpoint_xfer_start(struct hw_endpoint* ep, uint8_t* buffer, uint16_t total_len) { +void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) { hw_endpoint_lock_update(ep, 1); if (ep->active) { @@ -204,15 +239,25 @@ void hw_endpoint_xfer_start(struct hw_endpoint* ep, uint8_t* buffer, uint16_t to ep->remaining_len = total_len; ep->xferred_len = 0; ep->active = true; - ep->user_buf = buffer; - if (e15_is_bulkin_ep(ep)) { + if (ff != NULL) { + ep->user_fifo = ff; + ep->is_xfer_fifo = true; + } else { + ep->user_buf = buffer; + ep->is_xfer_fifo = false; + } + + #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + if (ep->e15_bulk_in) { usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; } if (e15_is_critical_frame_period(ep)) { - ep->pending = 1; - } else { + ep->pending = 1; // skip transfer if we are in critical frame period + } else + #endif + { hw_endpoint_start_next_buffer(ep); } @@ -220,31 +265,37 @@ void hw_endpoint_xfer_start(struct hw_endpoint* ep, uint8_t* buffer, uint16_t to } // sync endpoint buffer and return transferred bytes -static uint16_t __tusb_irq_path_func(sync_ep_buffer)(struct hw_endpoint* ep, uint8_t buf_id) { - uint32_t buf_ctrl = _hw_endpoint_buffer_control_get_value32(ep); - if (buf_id) buf_ctrl = buf_ctrl >> 16; +static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id, + bool is_rx) { + uint32_t buf_ctrl = *buf_ctrl_reg; + if (buf_id) { + buf_ctrl = buf_ctrl >> 16; + } - uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK; + const uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK; - if (!ep->rx) { + if (!is_rx) { // We are continuing a transfer here. If we are TX, we have successfully // sent some data can increase the length we have sent assert(!(buf_ctrl & USB_BUF_CTRL_FULL)); - - ep->xferred_len = (uint16_t) (ep->xferred_len + xferred_bytes); } else { // If we have received some data, so can increase the length // we have received AFTER we have copied it to the user buffer at the appropriate offset assert(buf_ctrl & USB_BUF_CTRL_FULL); - unaligned_memcpy(ep->user_buf, ep->hw_data_buf + buf_id * 64, xferred_bytes); - ep->xferred_len = (uint16_t) (ep->xferred_len + xferred_bytes); - ep->user_buf += xferred_bytes; + uint8_t *hw_buf = ep->hw_data_buf + buf_id * 64; + if (ep->is_xfer_fifo) { + // not in sram, may mess up timing with E15 workaround + tu_hwfifo_read_to_fifo(hw_buf, ep->user_fifo, xferred_bytes, NULL); + } else { + unaligned_memcpy(ep->user_buf, hw_buf, xferred_bytes); + ep->user_buf += xferred_bytes; + } } + ep->xferred_len += xferred_bytes; // Short packet if (xferred_bytes < ep->wMaxPacketSize) { - pico_trace(" Short packet on buffer %d with %u bytes\r\n", buf_id, xferred_bytes); // Reduce total length as this is last packet ep->remaining_len = 0; } @@ -252,21 +303,38 @@ static uint16_t __tusb_irq_path_func(sync_ep_buffer)(struct hw_endpoint* ep, uin return xferred_bytes; } -static void __tusb_irq_path_func(_hw_endpoint_xfer_sync)(struct hw_endpoint* ep) { - // Update hw endpoint struct with info from hardware - // after a buff status interrupt +// Update hw endpoint struct with info from hardware after a buff status interrupt +static void __tusb_irq_path_func(sync_xfer)(hw_endpoint_t *ep) { + // const uint8_t ep_num = tu_edpt_number(ep->ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr); - uint32_t __unused buf_ctrl = _hw_endpoint_buffer_control_get_value32(ep); - TU_LOG(3, " Sync BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl)); + io_rw_32 *buf_ctrl_reg; + io_rw_32 *ep_ctrl_reg; + bool is_rx; - // always sync buffer 0 - uint16_t buf0_bytes = sync_ep_buffer(ep, 0); + #if CFG_TUH_ENABLED + const bool is_host = rp2usb_is_host_mode(); + if (is_host) { + buf_ctrl_reg = hwbuf_ctrl_reg_host(ep); + ep_ctrl_reg = hwep_ctrl_reg_host(ep); + is_rx = (dir == TUSB_DIR_IN); + } else + #endif + { + buf_ctrl_reg = hwbuf_ctrl_reg_device(ep); + ep_ctrl_reg = hwep_ctrl_reg_device(ep); + is_rx = (dir == TUSB_DIR_OUT); + } + + TU_LOG(3, " Sync BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(*buf_ctrl_reg), + tu_u32_high16(*buf_ctrl_reg)); + uint16_t buf0_bytes = sync_ep_buffer(ep, buf_ctrl_reg, 0, is_rx); // always sync buffer 0 // sync buffer 1 if double buffered - if ((*ep->endpoint_control) & EP_CTRL_DOUBLE_BUFFERED_BITS) { + if (ep_ctrl_reg != NULL && (*ep_ctrl_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS) { if (buf0_bytes == ep->wMaxPacketSize) { // sync buffer 1 if not short packet - sync_ep_buffer(ep, 1); + sync_ep_buffer(ep, buf_ctrl_reg, 1, is_rx); } else { // short packet on buffer 0 // TODO couldn't figure out how to handle this case which happen with net_lwip_webserver example @@ -287,7 +355,8 @@ static void __tusb_irq_path_func(_hw_endpoint_xfer_sync)(struct hw_endpoint* ep) ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER); ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER; - _hw_endpoint_buffer_control_set_value32(ep, 0); + io_rw_32 *buf_ctrl_reg = is_host ? hwbuf_ctrl_reg_host(ep) : hwbuf_ctrl_reg_device(ep); + hwbuf_ctrl_set(buf_ctrl_reg, 0); usb_hw->abort &= ~TU_BIT(ep_id); @@ -307,8 +376,7 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) { panic("Can't continue xfer on inactive ep %02X", ep->ep_addr); } - // Update EP struct from hardware state - _hw_endpoint_xfer_sync(ep); + sync_xfer(ep); // Update EP struct from hardware state // Now we have synced our state with the hardware. Is there more data to transfer? // If we are done then notify tinyusb @@ -318,9 +386,12 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) { hw_endpoint_lock_update(ep, -1); return true; } else { + #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX if (e15_is_critical_frame_period(ep)) { ep->pending = 1; - } else { + } else + #endif + { hw_endpoint_start_next_buffer(ep); } } @@ -335,6 +406,7 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) { //--------------------------------------------------------------------+ #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX +// E15 is fixed with RP2350 /* Don't mark IN buffers as available during the last 200us of a full-speed frame. This avoids a situation seen with the USB2.0 hub on a Raspberry @@ -354,16 +426,12 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) { volatile uint32_t e15_last_sof = 0; -// check if Errata 15 is needed for this endpoint i.e device bulk-in -static bool __tusb_irq_path_func(e15_is_bulkin_ep)(struct hw_endpoint* ep) { - return (!is_host_mode() && tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN && - ep->transfer_type == TUSB_XFER_BULK); -} - // check if we need to apply Errata 15 workaround : i.e // Endpoint is BULK IN and is currently in critical frame period i.e 20% of last usb frame static bool __tusb_irq_path_func(e15_is_critical_frame_period)(struct hw_endpoint* ep) { - TU_VERIFY(e15_is_bulkin_ep(ep)); + if (!ep->e15_bulk_in) { + return false; + } /* Avoid the last 200us (uframe 6.5-7) of a frame, up to the EOF2 point. * The device state machine cannot recover from receiving an incorrect PID diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index d4d29a816..c03dc34b2 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -1,29 +1,44 @@ #ifndef RP2040_COMMON_H_ #define RP2040_COMMON_H_ -#if defined(RP2040_USB_HOST_MODE) && defined(RP2040_USB_DEVICE_MODE) -#error TinyUSB device and host mode not supported at the same time -#endif - -#include "common/tusb_common.h" - #include "pico.h" #include "hardware/structs/usb.h" #include "hardware/irq.h" #include "hardware/resets.h" #include "hardware/timer.h" -#if defined(PICO_RP2040_USB_DEVICE_ENUMERATION_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX) -#define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX PICO_RP2040_USB_DEVICE_ENUMERATION_FIX +#include "common/tusb_common.h" +#include "osal/osal.h" +#include "common/tusb_fifo.h" + +#if defined(RP2040_USB_HOST_MODE) && defined(RP2040_USB_DEVICE_MODE) + #error TinyUSB device and host mode not supported at the same time +#endif + +// E5 and E15 only apply to RP2040 +#if defined(PICO_RP2040) && PICO_RP2040 == 1 + // RP2040 E5: USB device fails to exit RESET state on busy USB bus. + #if defined(PICO_RP2040_USB_DEVICE_ENUMERATION_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX) + #define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX PICO_RP2040_USB_DEVICE_ENUMERATION_FIX + #endif + + // RP2040 E15: USB Device controller will hang if certain bus errors occur during an IN transfer. + #if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX) + #define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX PICO_RP2040_USB_DEVICE_UFRAME_FIX + #endif +#endif + +#ifndef TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX + #define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX 0 #endif -#if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX) -#define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX PICO_RP2040_USB_DEVICE_UFRAME_FIX +#ifndef TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + #define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX 0 #endif #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX -#undef PICO_RP2040_USB_FAST_IRQ -#define PICO_RP2040_USB_FAST_IRQ 1 + #undef PICO_RP2040_USB_FAST_IRQ + #define PICO_RP2040_USB_FAST_IRQ 1 #endif #ifndef PICO_RP2040_USB_FAST_IRQ @@ -36,6 +51,9 @@ #define __tusb_irq_path_func(x) x #endif +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ #define usb_hw_set ((usb_hw_t *) hw_set_alias_untyped(usb_hw)) #define usb_hw_clear ((usb_hw_t *) hw_clear_alias_untyped(usb_hw)) @@ -43,63 +61,48 @@ #define pico_trace(...) TU_LOG(3, __VA_ARGS__) // Hardware information per endpoint -typedef struct hw_endpoint -{ - // Is this a valid struct - bool configured; - - // Transfer direction (i.e. IN is rx for host but tx for device) - // allows us to common up transfer functions - bool rx; - - uint8_t ep_addr; - uint8_t next_pid; - - // Endpoint control register - io_rw_32 *endpoint_control; - - // Buffer control register - io_rw_32 *buffer_control; - - // Buffer pointer in usb dpram - uint8_t *hw_data_buf; - - // User buffer in main memory - uint8_t *user_buf; - - // Current transfer information - uint16_t remaining_len; - uint16_t xferred_len; +typedef struct hw_endpoint { + uint8_t ep_addr; + uint8_t next_pid; + bool active; // transferring data + bool is_xfer_fifo; // transfer using fifo - // Data needed from EP descriptor - uint16_t wMaxPacketSize; - - // Endpoint is in use - bool active; - - // Interrupt, bulk, etc - uint8_t transfer_type; - - // Transfer scheduled but not active - uint8_t pending; +#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + bool e15_bulk_in; // Errata15 device bulk in + uint8_t pending; // Transfer scheduled but not active +#endif #if CFG_TUH_ENABLED - // Only needed for host - uint8_t dev_addr; - - // If interrupt endpoint - uint8_t interrupt_num; + bool configured; // Is this a valid struct + uint8_t dev_addr; + uint8_t interrupt_num; // for host interrupt endpoints #endif + uint16_t wMaxPacketSize; + uint8_t *hw_data_buf; // Buffer pointer in usb dpram + + // transfer info + union { + uint8_t *user_buf; // User buffer in main memory + tu_fifo_t *user_fifo; + }; + uint16_t remaining_len; + uint16_t xferred_len; + } hw_endpoint_t; #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX extern volatile uint32_t e15_last_sof; #endif -void rp2040_usb_init(void); +void rp2usb_init(void); + +// if usb hardware is in host mode +TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) { + return (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) ? true : false; +} -void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, uint16_t total_len); +void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len); bool hw_endpoint_xfer_continue(struct hw_endpoint *ep); void hw_endpoint_reset_transfer(struct hw_endpoint *ep); void hw_endpoint_start_next_buffer(struct hw_endpoint *ep); @@ -110,34 +113,60 @@ TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct // sense to have worker and IRQ on same core, however I think using critsec is about equivalent. } -void _hw_endpoint_buffer_control_update32(struct hw_endpoint *ep, uint32_t and_mask, uint32_t or_mask); +// #if CFG_TUD_ENABLED +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_device(struct hw_endpoint *ep) { + uint8_t const epnum = tu_edpt_number(ep->ep_addr); + const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); + if (epnum == 0) { + // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled + return NULL; + } + return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_ctrl[epnum - 1].in : &usb_dpram->ep_ctrl[epnum - 1].out; +} -TU_ATTR_ALWAYS_INLINE static inline uint32_t _hw_endpoint_buffer_control_get_value32 (struct hw_endpoint *ep) -{ - return *ep->buffer_control; +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_device(struct hw_endpoint *ep) { + const uint8_t epnum = tu_edpt_number(ep->ep_addr); + const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); + return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_buf_ctrl[epnum].in : &usb_dpram->ep_buf_ctrl[epnum].out; } +// #endif -TU_ATTR_ALWAYS_INLINE static inline void _hw_endpoint_buffer_control_set_value32 (struct hw_endpoint *ep, uint32_t value) -{ - _hw_endpoint_buffer_control_update32(ep, 0, value); +#if CFG_TUH_ENABLED +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_host(struct hw_endpoint *ep) { + if (tu_edpt_number(ep->ep_addr) == 0) { + return &usbh_dpram->epx_ctrl; + } + return &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl; } -TU_ATTR_ALWAYS_INLINE static inline void _hw_endpoint_buffer_control_set_mask32 (struct hw_endpoint *ep, uint32_t value) -{ - _hw_endpoint_buffer_control_update32(ep, ~value, value); +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_host(struct hw_endpoint *ep) { + if (tu_edpt_number(ep->ep_addr) == 0) { + return &usbh_dpram->epx_buf_ctrl; + } + return &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl; } +#endif -TU_ATTR_ALWAYS_INLINE static inline void _hw_endpoint_buffer_control_clear_mask32 (struct hw_endpoint *ep, uint32_t value) -{ - _hw_endpoint_buffer_control_update32(ep, ~value, 0); +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +void hwbuf_ctrl_update(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask); + +TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set(io_rw_32 *buf_ctrl_reg, uint32_t value) { + hwbuf_ctrl_update(buf_ctrl_reg, 0, value); } -static inline uintptr_t hw_data_offset (uint8_t *buf) -{ - // Remove usb base from buffer pointer - return (uintptr_t) buf ^ (uintptr_t) usb_dpram; +TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set_mask(io_rw_32 *buf_ctrl_reg, uint32_t value) { + hwbuf_ctrl_update(buf_ctrl_reg, ~value, value); } -extern const char *ep_dir_string[]; +TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_clear_mask(io_rw_32 *buf_ctrl_reg, uint32_t value) { + hwbuf_ctrl_update(buf_ctrl_reg, ~value, 0); +} + +static inline uintptr_t hw_data_offset(uint8_t *buf) { + // Remove usb base from buffer pointer + return (uintptr_t)buf ^ (uintptr_t)usb_dpram; +} #endif diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c index 22a9e4af8..a6abc6244 100644 --- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c +++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c @@ -833,7 +833,7 @@ void dcd_int_disable(uint8_t rhport) { fsdev_int_disable(rhport); } - #if defined(USB_BCDR_DPPU) || defined(SYSCFG_PMC_USB_PU) + #if defined(USB_BCDR_DPPU) || defined(SYSCFG_PMC_USB_PU) || defined(EXTEN_USBD_PU_EN) void dcd_connect(uint8_t rhport) { fsdev_connect(rhport); } diff --git a/src/portable/st/stm32_fsdev/fsdev_common.h b/src/portable/st/stm32_fsdev/fsdev_common.h index c53e345b0..b749a92ff 100644 --- a/src/portable/st/stm32_fsdev/fsdev_common.h +++ b/src/portable/st/stm32_fsdev/fsdev_common.h @@ -63,23 +63,21 @@ TU_VERIFY_STATIC(FSDEV_BTABLE_BASE % 8 == 0, "BTABLE base must be aligned to 8 b // CFG_TUSB_FSDEV_PMA_SIZE is PMA buffer size in bytes. // - 512-byte devices, access with a stride of two words (use every other 16-bit address) -// - 1024-byte devices, access with a stride of one word (use every 16-bit address) +// - 1024-byte devices, access with a stride of one word (use every 16-bit address) or 32-bit address // - 2048-byte devices, access with 32-bit address - -// For purposes of accessing the packet -#if CFG_TUSB_FSDEV_PMA_SIZE == 512 - // 1x16 bit / word access scheme - #define FSDEV_PMA_STRIDE 2 - #define pma_access_scheme TU_ATTR_ALIGNED(4) -#elif CFG_TUSB_FSDEV_PMA_SIZE == 1024 - // 2x16 bit / word access scheme - #define FSDEV_PMA_STRIDE 1 - #define pma_access_scheme -#elif CFG_TUSB_FSDEV_PMA_SIZE == 2048 +#if CFG_TUSB_FSDEV_PMA_SIZE == 2048 || TU_CHECK_MCU(OPT_MCU_STM32U0) // 32 bit access scheme #define FSDEV_BUS_32BIT #define FSDEV_PMA_STRIDE 1 #define pma_access_scheme +#elif CFG_TUSB_FSDEV_PMA_SIZE == 1024 + // 2x16 bit / word access scheme + #define FSDEV_PMA_STRIDE 1 + #define pma_access_scheme +#elif CFG_TUSB_FSDEV_PMA_SIZE == 512 + // 1x16 bit / word access scheme + #define FSDEV_PMA_STRIDE 2 + #define pma_access_scheme TU_ATTR_ALIGNED(4) #endif // The fsdev_bus_t type can be used for both register and PMA access necessities diff --git a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c index acdeccf6d..1813ef70b 100644 --- a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c +++ b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c @@ -223,9 +223,13 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { tu_memclr(&_hcd_data, sizeof(_hcd_data)); + // Clear pending interrupts + // Normally no interrupts should be pending here since we just reset the core, + // but device mode suspend needs to cleared by WKUP flag + FSDEV_REG->ISTR = 0; + // Enable interrupts for host mode - FSDEV_REG->CNTR |= USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SOFM | USB_CNTR_SUSPM | - USB_CNTR_WKUPM | USB_CNTR_ERRM | USB_CNTR_PMAOVRM; + FSDEV_REG->CNTR |= USB_CNTR_DCON | USB_CNTR_CTRM | USB_CNTR_SOFM | USB_CNTR_ERRM | USB_CNTR_PMAOVRM; // Initialize port state _hcd_data.connected = false; diff --git a/src/portable/sunxi/dcd_sunxi_musb.c b/src/portable/sunxi/dcd_sunxi_musb.c index b413121a5..9fac0bc1c 100644 --- a/src/portable/sunxi/dcd_sunxi_musb.c +++ b/src/portable/sunxi/dcd_sunxi_musb.c @@ -36,9 +36,6 @@ #include <device/dcd.h> #include "musb_def.h" -//#include "bsp/board_api.h" -extern uint32_t board_millis(void); // TODO remove - typedef uint32_t u32; typedef uint16_t u16; typedef uint8_t u8; diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 44f7137f9..8685ec6dc 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -77,9 +77,7 @@ CFG_TUD_MEM_SECTION static struct { TUD_EPBUF_DEF(setup_packet, 8); } _dcd_usbbuf; -static tud_configure_dwc2_t _tud_cfg = { - .bm_double_buffered = 0 -}; +static tud_configure_dwc2_t _tud_cfg = CFG_TUD_CONFIGURE_DWC2_DEFAULT; TU_ATTR_ALWAYS_INLINE static inline uint8_t dwc2_ep_count(const dwc2_regs_t* dwc2) { #if TU_CHECK_MCU(OPT_MCU_GD32VF103) @@ -444,14 +442,14 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { tu_memclr(&_dcd_data, sizeof(_dcd_data)); // Core Initialization - const bool is_highspeed = dwc2_core_is_highspeed(dwc2, TUSB_ROLE_DEVICE); + const bool is_hs_phy = dwc2_core_is_highspeed_phy(dwc2, TUD_OPT_HIGH_SPEED); const bool is_dma = dma_device_enabled(dwc2); - TU_ASSERT(dwc2_core_init(rhport, is_highspeed, is_dma)); + TU_ASSERT(dwc2_core_init(rhport, is_hs_phy, is_dma)); //------------- 7.1 Device Initialization -------------// // Set device max speed uint32_t dcfg = dwc2->dcfg & ~DCFG_DSPD_Msk; - if (is_highspeed) { + if (is_hs_phy) { // dcfg Highspeed's mask is 0 // XCVRDLY: transceiver delay between xcvr_sel and txvalid during device chirp is required @@ -472,13 +470,16 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { // Force device mode dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_FHMOD) | GUSBCFG_FDMOD; - // Clear A override, force B Valid - dwc2->gotgctl = (dwc2->gotgctl & ~GOTGCTL_AVALOEN) | GOTGCTL_BVALOEN | GOTGCTL_BVALOVAL; + // OTG Ctrl + uint32_t gotgctl = dwc2->gotgctl & ~GOTGCTL_AVALOEN; // Clear A-override + if (!_tud_cfg.vbus_sensing) { + gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_BVALOVAL; // force B Valid if not sensing VBus + } + dwc2->gotgctl = gotgctl; -#if CFG_TUSB_MCU == OPT_MCU_STM32N6 - // No hardware detection of Vbus B-session is available on the STM32N6 - dwc2->stm32_gccfg |= STM32_GCCFG_VBVALOVAL; -#endif + #ifdef TUP_USBIP_DWC2_STM32 + dwc2_stm32_gccfg_cfg(dwc2, _tud_cfg.vbus_sensing, false); + #endif // Enable required interrupts dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_USBRST | GINTMSK_ENUMDNEM | GINTMSK_WUIM; @@ -491,6 +492,12 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { return true; } +bool dcd_deinit(uint8_t rhport) { + dcd_disconnect(rhport); + dwc2_core_deinit(rhport); + return true; +} + void dcd_int_enable(uint8_t rhport) { dwc2_dcd_int_enable(rhport); } @@ -526,34 +533,38 @@ void dcd_remote_wakeup(uint8_t rhport) { } void dcd_connect(uint8_t rhport) { - (void) rhport; dwc2_regs_t* dwc2 = DWC2_REG(rhport); #ifdef TUP_USBIP_DWC2_ESP32 - usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; - conf.pad_pull_override = 0; - conf.dp_pullup = 0; - conf.dp_pulldown = 0; - conf.dm_pullup = 0; - conf.dm_pulldown = 0; - USB_WRAP.otg_conf = conf; + // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY + if (rhport == 0) { + usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; + conf.pad_pull_override = 0; + conf.dp_pullup = 0; + conf.dp_pulldown = 0; + conf.dm_pullup = 0; + conf.dm_pulldown = 0; + USB_WRAP.otg_conf = conf; + } #endif dwc2->dctl &= ~DCTL_SDIS; } void dcd_disconnect(uint8_t rhport) { - (void) rhport; dwc2_regs_t* dwc2 = DWC2_REG(rhport); #ifdef TUP_USBIP_DWC2_ESP32 - usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; - conf.pad_pull_override = 1; - conf.dp_pullup = 0; - conf.dp_pulldown = 1; - conf.dm_pullup = 0; - conf.dm_pulldown = 1; - USB_WRAP.otg_conf = conf; + // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY + if (rhport == 0) { + usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; + conf.pad_pull_override = 1; + conf.dp_pullup = 0; + conf.dp_pulldown = 1; + conf.dm_pullup = 0; + conf.dm_pulldown = 1; + USB_WRAP.otg_conf = conf; + } #endif dwc2->dctl |= DCTL_SDIS; @@ -780,7 +791,7 @@ static void handle_bus_reset(uint8_t rhport) { dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); } - dwc2->gintmsk |= GINTMSK_OEPINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; + dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_OEPINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; } static void handle_enum_done(uint8_t rhport) { @@ -1180,6 +1191,7 @@ void dcd_int_handler(uint8_t rhport) { const uint32_t otg_int = dwc2->gotgint; if (otg_int & GOTGINT_SEDET) { + dwc2->gintmsk &= ~GINTMSK_OTGINT; dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true); } diff --git a/src/portable/synopsys/dwc2/dwc2_at32.h b/src/portable/synopsys/dwc2/dwc2_at32.h index 513495eb1..fa6d10c12 100644 --- a/src/portable/synopsys/dwc2/dwc2_at32.h +++ b/src/portable/synopsys/dwc2/dwc2_at32.h @@ -61,6 +61,11 @@ #define OTG1_FIFO_SIZE 1280 #define OTG1_IRQn OTGFS1_IRQn #define DWC2_OTG1_REG_BASE 0x50000000UL +#elif CFG_TUSB_MCU == OPT_MCU_AT32F45X + #include <at32f45x.h> + #define OTG1_FIFO_SIZE 1280 + #define OTG1_IRQn OTGFS1_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL #endif #ifdef __cplusplus @@ -107,6 +112,12 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_ } } +// MCU specific PHY deinit, disable PHY power +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { + (void) hs_phy_type; + dwc2->stm32_gccfg &= ~(STM32_GCCFG_PWRDWN | STM32_GCCFG_DCDEN | STM32_GCCFG_PDEN); +} + // MCU specific PHY update, it is called AFTER init() and core reset TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { (void) dwc2; diff --git a/src/portable/synopsys/dwc2/dwc2_bcm.h b/src/portable/synopsys/dwc2/dwc2_bcm.h index df6d4a852..00842bba2 100644 --- a/src/portable/synopsys/dwc2/dwc2_bcm.h +++ b/src/portable/synopsys/dwc2/dwc2_bcm.h @@ -73,6 +73,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) // nothing to do } +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // nothing to do +} + // MCU specific PHY update, it is called AFTER init() and core reset static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/dwc2_common.c b/src/portable/synopsys/dwc2/dwc2_common.c index a7e6188df..33eabaeab 100644 --- a/src/portable/synopsys/dwc2/dwc2_common.c +++ b/src/portable/synopsys/dwc2/dwc2_common.c @@ -39,14 +39,11 @@ static void reset_core(dwc2_regs_t* dwc2) { while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) { } - // load gsnpsid (it is not readable after reset is asserted) - const uint32_t gsnpsid = dwc2->gsnpsid; - - // reset core - dwc2->grstctl |= GRSTCTL_CSRST; + const uint32_t gsnpsid = dwc2->gsnpsid; // preload gsnpsid which is not readable while resetting + dwc2->grstctl |= GRSTCTL_CSRST; // reset core if ((gsnpsid & DWC2_CORE_REV_MASK) < (DWC2_CORE_REV_4_20a & DWC2_CORE_REV_MASK)) { - // prior v4.20a: CSRST is self-clearing and the core clears this bit after all the necessary logic is reset in + // prior v4.20a: CSRST is self-clearing, and the core clears this bit after all the necessary logic is reset in // the core, which can take several clocks, depending on the current state of the core. Once this bit has been // cleared, the software must wait at least 3 PHY clocks before accessing the PHY domain (synchronization delay). while (dwc2->grstctl & GRSTCTL_CSRST) {} @@ -60,6 +57,7 @@ static void reset_core(dwc2_regs_t* dwc2) { while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) {} // wait for AHB master IDLE } +// Dedicated FS PHY is internal with a clock 48Mhz. static void phy_fs_init(dwc2_regs_t* dwc2) { TU_LOG(DWC2_COMMON_DEBUG, "Fullspeed PHY init\r\n"); @@ -86,6 +84,12 @@ static void phy_fs_init(dwc2_regs_t* dwc2) { dwc2_phy_update(dwc2, GHWCFG2_HSPHY_NOT_SUPPORTED); } +/* dwc2 has 2 highspeed PHYs options + * - UTMI+ is internal highspeed PHY, can be clocked at 30 Mhz (8-bit) or 60 Mhz (16-bit). + * - ULPI is external highspeed PHY, clocked at 60Mhz with 8-bit interface. + * + * In addition, UTMI+/ULPI can be shared to run at fullspeed mode with 48Mhz + */ static void phy_hs_init(dwc2_regs_t* dwc2) { uint32_t gusbcfg = dwc2->gusbcfg; const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; @@ -179,32 +183,20 @@ static bool check_dwc2(dwc2_regs_t* dwc2) { //-------------------------------------------------------------------- // //-------------------------------------------------------------------- -bool dwc2_core_is_highspeed(dwc2_regs_t* dwc2, tusb_role_t role) { - (void)dwc2; -#if CFG_TUD_ENABLED - if (role == TUSB_ROLE_DEVICE && !TUD_OPT_HIGH_SPEED) { - return false; - } -#endif -#if CFG_TUH_ENABLED - if (role == TUSB_ROLE_HOST && !TUH_OPT_HIGH_SPEED) { - return false; - } -#endif +bool dwc2_core_is_highspeed_phy(dwc2_regs_t* dwc2, bool prefer_hs_phy) { + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + const bool has_hs_phy = (ghwcfg2.hs_phy_type != GHWCFG2_HSPHY_NOT_SUPPORTED); - const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; - return ghwcfg2.hs_phy_type != GHWCFG2_HSPHY_NOT_SUPPORTED; + if (prefer_hs_phy) { + return has_hs_phy; + } else { + const bool has_fs_phy = (ghwcfg2.fs_phy_type != GHWCFG2_FSPHY_NOT_SUPPORTED); + // false if has fs phy, otherwise true since hs phy is the only available phy + return !has_fs_phy && has_hs_phy; + } } -/* dwc2 has several PHYs option - * - UTMI+ is internal highspeed PHY, clock can be 30 Mhz (8-bit) or 60 Mhz (16-bit) - * - ULPI is external highspeed PHY, clock is 60Mhz with only 8-bit interface - * - Dedicated FS PHY is internal with clock 48Mhz. - * - * In addition, UTMI+/ULPI can be shared to run at fullspeed mode with 48Mhz - * -*/ -bool dwc2_core_init(uint8_t rhport, bool is_highspeed, bool is_dma) { +bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); // Check Synopsys ID register, failed if controller clock/power is not enabled @@ -213,7 +205,7 @@ bool dwc2_core_init(uint8_t rhport, bool is_highspeed, bool is_dma) { // disable global interrupt dwc2->gahbcfg &= ~GAHBCFG_GINT; - if (is_highspeed) { + if (is_hs_phy) { phy_hs_init(dwc2); } else { phy_fs_init(dwc2); @@ -251,6 +243,24 @@ bool dwc2_core_init(uint8_t rhport, bool is_highspeed, bool is_dma) { return true; } +void dwc2_core_deinit(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + // Disable global interrupt + dwc2->gahbcfg &= ~GAHBCFG_GINT; + + // Reset core: this also flushes FIFOs and clears all interrupt registers + reset_core(dwc2); + + // Stop PHY clock and gate HCLK for power saving (per databook chapter 14) + dwc2->pcgcctl |= PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK; + + // MCU-specific PHY deinit (disable PHY power) + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + const uint8_t hs_phy_type = (dwc2->gusbcfg & GUSBCFG_PHYSEL) ? GHWCFG2_HSPHY_NOT_SUPPORTED : ghwcfg2.hs_phy_type; + dwc2_phy_deinit(dwc2, hs_phy_type); +} + // void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr) { // (void) in_isr; // dwc2_regs_t * const dwc2 = DWC2_REG(rhport); diff --git a/src/portable/synopsys/dwc2/dwc2_common.h b/src/portable/synopsys/dwc2/dwc2_common.h index 428304ba9..9f28ab2e0 100644 --- a/src/portable/synopsys/dwc2/dwc2_common.h +++ b/src/portable/synopsys/dwc2/dwc2_common.h @@ -42,6 +42,7 @@ // - _dwc2_controller[]: array of controllers // - DWC2_EP_MAX: largest EP counts of all controllers // - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset +// - dwc2_phy_deinit(dwc2, hs_phy_type): phy deinit to disable PHY power, only deinit the phy used by core // - dwc2_dcd_int_enable/dwc2_dcd_int_disable // - dwc2_remote_wakeup_delay @@ -84,8 +85,10 @@ TU_ATTR_ALWAYS_INLINE static inline dwc2_regs_t* DWC2_REG(uint8_t rhport) { return (dwc2_regs_t*)_dwc2_controller[rhport].reg_base; } -bool dwc2_core_is_highspeed(dwc2_regs_t* dwc2, tusb_role_t role); -bool dwc2_core_init(uint8_t rhport, bool is_highspeed, bool is_dma); +// check if highspeed phy should be used +bool dwc2_core_is_highspeed_phy(dwc2_regs_t* dwc2, bool prefer_hs_phy); +bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma); +void dwc2_core_deinit(uint8_t rhport); void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr); //--------------------------------------------------------------------+ diff --git a/src/portable/synopsys/dwc2/dwc2_efm32.h b/src/portable/synopsys/dwc2/dwc2_efm32.h index 0e3570cbb..e1cb7c769 100644 --- a/src/portable/synopsys/dwc2/dwc2_efm32.h +++ b/src/portable/synopsys/dwc2/dwc2_efm32.h @@ -72,6 +72,14 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) USB->ROUTE = USB_ROUTE_PHYPEN; } +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // Disable PHY pin + USB->ROUTE = 0; +} + // MCU specific PHY update, it is called AFTER init() and core reset static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/dwc2_esp32.h b/src/portable/synopsys/dwc2/dwc2_esp32.h index a4e0d1770..ff9f216bd 100644 --- a/src/portable/synopsys/dwc2/dwc2_esp32.h +++ b/src/portable/synopsys/dwc2/dwc2_esp32.h @@ -121,6 +121,13 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_ } +// MCU specific PHY deinit, disable PHY power +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + (void)dwc2; + (void)hs_phy_type; + // PHY managed by ESP-IDF +} + // MCU specific PHY update, it is called AFTER init() and core reset TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { (void)dwc2; diff --git a/src/portable/synopsys/dwc2/dwc2_gd32.h b/src/portable/synopsys/dwc2/dwc2_gd32.h index 0375fffe4..ccbf93a76 100644 --- a/src/portable/synopsys/dwc2/dwc2_gd32.h +++ b/src/portable/synopsys/dwc2/dwc2_gd32.h @@ -85,6 +85,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) // nothing to do } +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // nothing to do +} + // MCU specific PHY update, it is called AFTER init() and core reset static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/dwc2_info.md b/src/portable/synopsys/dwc2/dwc2_info.md index f655e4dba..f83007b8c 100644 --- a/src/portable/synopsys/dwc2/dwc2_info.md +++ b/src/portable/synopsys/dwc2/dwc2_info.md @@ -1,58 +1,58 @@ -| | AT32 F405 FS | AT32 F405 HS | AT32 F415 | BCM2711 (Pi4) | EFM32GG | ESP32-S2/S3 | ESP32-P4 | nRF54 | ST F207/F407/411/429 FS | ST F407/429 HS | ST F412/76x FS | ST F723/L4P5 FS | ST F723 HS | ST F76x HS | ST H743/H750 | ST L476 FS | ST U5A5/H7RS/N6 HS | XMC4500 | GD32VF103 | -|:---------------------------|:---------------|:---------------|:------------|:----------------|:-------------|:--------------|:-------------|:-------------|:--------------------------|:-----------------|:-----------------|:------------------|:-------------|:-------------|:---------------|:-------------|:---------------------|:-------------|:------------| -| GUID | 0x00002000 | 0x00000000 | 0x00001000 | 0x2708A000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00001200 | 0x00001100 | 0x00002000 | 0x00003000 | 0x00003100 | 0x00002100 | 0x00002300 | 0x00002000 | 0x00005000 | 0x00AEC000 | 0x00001000 | -| GSNPSID | 0x4F54400A | 0x4F54400A | 0x4F54400A | 0x4F54280A | 0x4F54330A | 0x4F54400A | 0x4F54400A | 0x4F54430A | 0x4F54281A | 0x4F54281A | 0x4F54320A | 0x4F54330A | 0x4F54330A | 0x4F54320A | 0x4F54330A | 0x4F54310A | 0x4F54411A | 0x4F54292A | 0x00000000 | -| - specs version | 4.00a | 4.00a | 4.00a | 2.80a | 3.30a | 4.00a | 4.00a | 4.30a | 2.81a | 2.81a | 3.20a | 3.30a | 3.30a | 3.20a | 3.30a | 3.10a | 4.11a | 2.92a | 0.00W | -| GHWCFG1 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0xAA555000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | -| GHWCFG2 | 0x228FDD00 | 0x229FDDD0 | 0x228DCD00 | 0x228DDD50 | 0x228F5910 | 0x224DD930 | 0x215FFFD0 | 0x228BFC72 | 0x229DCD20 | 0x229ED590 | 0x229ED520 | 0x229ED520 | 0x229FE1D0 | 0x229FE190 | 0x229FE190 | 0x229ED520 | 0x228FE052 | 0x228F5930 | 0x00000000 | -| - op_mode | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | HNP SRP | HNP SRP | -| - arch | Slave only | DMA internal | Slave only | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | Slave only | DMA internal | Slave only | Slave only | DMA internal | DMA internal | DMA internal | Slave only | DMA internal | DMA internal | Slave only | -| - single_point | hub | hub | hub | hub | hub | n/a | hub | n/a | n/a | hub | n/a | n/a | hub | hub | hub | n/a | hub | n/a | hub | -| - hs_phy_type | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a | UTMI+/ULPI | UTMI+ | n/a | ULPI | n/a | n/a | UTMI+/ULPI | ULPI | ULPI | n/a | UTMI+ | n/a | n/a | -| - fs_phy_type | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Shared ULPI | n/a | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | n/a | Dedicated | n/a | -| - num_dev_ep | 7 | 7 | 3 | 7 | 6 | 6 | 15 | 15 | 3 | 5 | 5 | 5 | 8 | 8 | 8 | 5 | 8 | 6 | 0 | -| - num_host_ch | 15 | 15 | 7 | 7 | 13 | 7 | 15 | 15 | 7 | 11 | 11 | 11 | 15 | 15 | 15 | 11 | 15 | 13 | 0 | -| - period_channel_support | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - enable_dynamic_fifo | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - mul_proc_intrpt | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | -| - reserved21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - nptx_q_depth | 8 | 8 | 8 | 8 | 8 | 4 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 | -| - ptx_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 | -| - token_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 0 | -| - otg_enable_ic_usb | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| GHWCFG3 | 0x020004E8 | 0x03F006E8 | 0x020004E8 | 0x0FF000E8 | 0x01F204E8 | 0x00C804B5 | 0x03805EB5 | 0x0BEAC0E8 | 0x020001E8 | 0x03F403E8 | 0x0200D1E8 | 0x0200D1E8 | 0x03EED2E8 | 0x03EED2E8 | 0x03B8D2E8 | 0x0200D1E8 | 0x03B882E8 | 0x027A01E5 | 0x00000000 | -| - xfer_size_width | 8 | 8 | 8 | 8 | 8 | 5 | 5 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 5 | 0 | -| - packet_size_width | 6 | 6 | 6 | 6 | 6 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 0 | -| - otg_enable | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - i2c_enable | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | -| - vendor_ctrl_itf | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | -| - optional_feature_removed | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - synch_reset | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - otg_adp_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | -| - otg_enable_hsic | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - battery_charger_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | -| - lpm_mode | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | -| - dfifo_depth | 512 | 1008 | 512 | 4080 | 498 | 200 | 896 | 3050 | 512 | 1012 | 512 | 512 | 1006 | 1006 | 952 | 512 | 952 | 634 | 0 | -| GHWCFG4 | 0x1FF0A020 | 0x1FF0A020 | 0x0000000F | 0x1FF00020 | 0x1BF08030 | 0xD3F0A030 | 0xDFF1A030 | 0x1E10AA60 | 0x0FF08030 | 0x17F00030 | 0x17F08030 | 0x17F08030 | 0x23F00030 | 0x23F00030 | 0xE3F00030 | 0x17F08030 | 0xE2103E30 | 0xDBF08030 | 0x00000000 | -| - num_dev_period_in_ep | 0 | 0 | 15 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - partial_powerdown | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - ahb_freq_min | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - extended_hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - reserved8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - enhanced_lpm_support1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | -| - service_interval_flow | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | -| - ipg_isoc_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | -| - acg_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | -| - enhanced_lpm_support | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | -| - phy_data_width | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | -| - ctrl_ep_num | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| - iddg_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - vbus_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | -| - a_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | -| - b_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | -| - session_end_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | -| - dedicated_fifos | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | -| - num_dev_in_eps | 7 | 7 | 0 | 7 | 6 | 4 | 7 | 7 | 3 | 5 | 5 | 5 | 8 | 8 | 8 | 5 | 8 | 6 | 0 | -| - dma_desc_enable | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | -| - dma_desc_dynamic | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | +| | AT32 F405 FS | AT32 F405 HS | AT32 F415 | BCM2711 (Pi4) | EFM32GG | ESP32-S2/S3 | ESP32-P4 | nRF54 | ST F407/429 HS | ST F207/F407/411/429 FS | ST L476 FS | ST F412/76x FS | ST F76x HS | ST H743/H750 | ST F723/L4P5 FS | ST F723 HS | ST H7RS FS | ST U5A5/H7RS/N6 HS | XMC4500 | GD32VF103 | +|:---------------------------|:---------------|:---------------|:------------|:----------------|:-------------|:--------------|:-------------|:-------------|:-----------------|:--------------------------|:-------------|:-----------------|:-------------|:---------------|:------------------|:-------------|:-------------|:---------------------|:-------------|:------------| +| GUID | 0x00002000 | 0x00000000 | 0x00001000 | 0x2708A000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00001100 | 0x00001200 | 0x00002000 | 0x00002000 | 0x00002100 | 0x00002300 | 0x00003000 | 0x00003100 | 0x00004000 | 0x00005000 | 0x00AEC000 | 0x00001000 | +| GSNPSID | 0x4F54400A | 0x4F54400A | 0x4F54400A | 0x4F54280A | 0x4F54330A | 0x4F54400A | 0x4F54400A | 0x4F54430A | 0x4F54281A | 0x4F54281A | 0x4F54310A | 0x4F54320A | 0x4F54320A | 0x4F54330A | 0x4F54330A | 0x4F54330A | 0x4F54411A | 0x4F54411A | 0x4F54292A | 0x00000000 | +| - specs version | 4.00a | 4.00a | 4.00a | 2.80a | 3.30a | 4.00a | 4.00a | 4.30a | 2.81a | 2.81a | 3.10a | 3.20a | 3.20a | 3.30a | 3.30a | 3.30a | 4.11a | 4.11a | 2.92a | 0.00W | +| GHWCFG1 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0xAA555000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | +| GHWCFG2 | 0x228FDD00 | 0x229FDDD0 | 0x228DCD00 | 0x228DDD50 | 0x228F5910 | 0x224DD930 | 0x215FFFD0 | 0x228BFC72 | 0x229ED590 | 0x229DCD20 | 0x229ED520 | 0x229ED520 | 0x229FE190 | 0x229FE190 | 0x229ED520 | 0x229FE1D0 | 0x229ED522 | 0x228FE052 | 0x228F5930 | 0x00000000 | +| - op_mode | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | noHNP noSRP | HNP SRP | HNP SRP | +| - arch | Slave only | DMA internal | Slave only | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | Slave only | Slave only | Slave only | DMA internal | DMA internal | Slave only | DMA internal | Slave only | DMA internal | DMA internal | Slave only | +| - single_point | hub | hub | hub | hub | hub | n/a | hub | n/a | hub | n/a | n/a | n/a | hub | hub | n/a | hub | n/a | hub | n/a | hub | +| - hs_phy_type | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a | UTMI+/ULPI | UTMI+ | ULPI | n/a | n/a | n/a | ULPI | ULPI | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a | +| - fs_phy_type | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Shared ULPI | n/a | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | n/a | Dedicated | n/a | +| - num_dev_ep | 7 | 7 | 3 | 7 | 6 | 6 | 15 | 15 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 | +| - num_host_ch | 15 | 15 | 7 | 7 | 13 | 7 | 15 | 15 | 11 | 7 | 11 | 11 | 15 | 15 | 11 | 15 | 11 | 15 | 13 | 0 | +| - period_channel_support | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - enable_dynamic_fifo | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - mul_proc_intrpt | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | +| - reserved21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - nptx_q_depth | 8 | 8 | 8 | 8 | 8 | 4 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 | +| - ptx_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 | +| - token_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 0 | +| - otg_enable_ic_usb | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| GHWCFG3 | 0x020004E8 | 0x03F006E8 | 0x020004E8 | 0x0FF000E8 | 0x01F204E8 | 0x00C804B5 | 0x03805EB5 | 0x0BEAC0E8 | 0x03F403E8 | 0x020001E8 | 0x0200D1E8 | 0x0200D1E8 | 0x03EED2E8 | 0x03B8D2E8 | 0x0200D1E8 | 0x03EED2E8 | 0x020081E8 | 0x03B882E8 | 0x027A01E5 | 0x00000000 | +| - xfer_size_width | 8 | 8 | 8 | 8 | 8 | 5 | 5 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 5 | 0 | +| - packet_size_width | 6 | 6 | 6 | 6 | 6 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 0 | +| - otg_enable | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - i2c_enable | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | +| - vendor_ctrl_itf | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | +| - optional_feature_removed | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - synch_reset | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - otg_adp_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | +| - otg_enable_hsic | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - battery_charger_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | +| - lpm_mode | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | +| - dfifo_depth | 512 | 1008 | 512 | 4080 | 498 | 200 | 896 | 3050 | 1012 | 512 | 512 | 512 | 1006 | 952 | 512 | 1006 | 512 | 952 | 634 | 0 | +| GHWCFG4 | 0x1FF0A020 | 0x1FF0A020 | 0x0000000F | 0x1FF00020 | 0x1BF08030 | 0xD3F0A030 | 0xDFF1A030 | 0x1E10AA60 | 0x17F00030 | 0x0FF08030 | 0x17F08030 | 0x17F08030 | 0x23F00030 | 0xE3F00030 | 0x17F08030 | 0x23F00030 | 0x1610B230 | 0xE2103E30 | 0xDBF08030 | 0x00000000 | +| - num_dev_period_in_ep | 0 | 0 | 15 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - partial_powerdown | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - ahb_freq_min | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - extended_hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - reserved8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - enhanced_lpm_support1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | +| - service_interval_flow | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| - ipg_isoc_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| - acg_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | +| - enhanced_lpm_support | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | +| - phy_data_width | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | +| - ctrl_ep_num | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - iddg_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - vbus_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | +| - a_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | +| - b_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | +| - session_end_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | +| - dedicated_fifos | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - num_dev_in_eps | 7 | 7 | 0 | 7 | 6 | 4 | 7 | 7 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 | +| - dma_desc_enable | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | +| - dma_desc_dynamic | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | diff --git a/src/portable/synopsys/dwc2/dwc2_info.py b/src/portable/synopsys/dwc2/dwc2_info.py index 8fbbc00a0..e6601f482 100755 --- a/src/portable/synopsys/dwc2/dwc2_info.py +++ b/src/portable/synopsys/dwc2/dwc2_info.py @@ -16,14 +16,16 @@ dwc2_reg_value = { 'ESP32-S2/S3': [0, 0x4F54400A, 0, 0x224DD930, 0x0C804B5, 0xD3F0A030], 'ESP32-P4': [0, 0x4F54400A, 0, 0x215FFFD0, 0x03805EB5, 0xDFF1A030], 'nRF54': [0, 0x4F54430A, 0xAA555000, 0x228BFC72, 0x0BEAC0E8, 0x1E10AA60], - 'ST F207/F407/411/429 FS': [0x1200, 0x4F54281A, 0, 0x229DCD20, 0x020001E8, 0x0FF08030], + # ST sort by GUID 'ST F407/429 HS': [0x1100, 0x4F54281A, 0, 0x229ED590, 0x03F403E8, 0x17F00030], + 'ST F207/F407/411/429 FS': [0x1200, 0x4F54281A, 0, 0x229DCD20, 0x020001E8, 0x0FF08030], + 'ST L476 FS': [0x2000, 0x4F54310A, 0, 0x229ED520, 0x0200D1E8, 0x17F08030], 'ST F412/76x FS': [0x2000, 0x4F54320A, 0, 0x229ED520, 0x0200D1E8, 0x17F08030], - 'ST F723/L4P5 FS': [0x3000, 0x4F54330A, 0, 0x229ED520, 0x0200D1E8, 0x17F08030], - 'ST F723 HS': [0x3100, 0x4F54330A, 0, 0x229FE1D0, 0x03EED2E8, 0x23F00030], 'ST F76x HS': [0x2100, 0x4F54320A, 0, 0x229FE190, 0x03EED2E8, 0x23F00030], 'ST H743/H750': [0x2300, 0x4F54330A, 0, 0x229FE190, 0x03B8D2E8, 0xE3F00030], - 'ST L476 FS': [0x2000, 0x4F54310A, 0, 0x229ED520, 0x0200D1E8, 0x17F08030], + 'ST F723/L4P5 FS': [0x3000, 0x4F54330A, 0, 0x229ED520, 0x0200D1E8, 0x17F08030], + 'ST F723 HS': [0x3100, 0x4F54330A, 0, 0x229FE1D0, 0x03EED2E8, 0x23F00030], + 'ST H7RS FS': [0x4000, 0x4F54411A, 0, 0x229ED522, 0x20081E8, 0x1610B230], 'ST U5A5/H7RS/N6 HS': [0x5000, 0x4F54411A, 0, 0x228FE052, 0x03B882E8, 0xE2103E30], 'XMC4500': [0xAEC000, 0x4F54292A, 0, 0x228F5930, 0x027A01E5, 0xDBF08030], 'GD32VF103': [0x1000, 0, 0, 0, 0, 0], diff --git a/src/portable/synopsys/dwc2/dwc2_nrf.h b/src/portable/synopsys/dwc2/dwc2_nrf.h index b93571f16..51f2d684f 100644 --- a/src/portable/synopsys/dwc2/dwc2_nrf.h +++ b/src/portable/synopsys/dwc2/dwc2_nrf.h @@ -52,6 +52,12 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_ (void)hs_phy_type; } +// MCU specific PHY deinit, disable PHY power +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + (void)dwc2; + (void)hs_phy_type; +} + // MCU specific PHY update, it is called AFTER init() and core reset TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { (void)dwc2; diff --git a/src/portable/synopsys/dwc2/dwc2_stm32.h b/src/portable/synopsys/dwc2/dwc2_stm32.h index 516eb021b..259ad21b9 100644 --- a/src/portable/synopsys/dwc2/dwc2_stm32.h +++ b/src/portable/synopsys/dwc2/dwc2_stm32.h @@ -85,8 +85,11 @@ extern "C" { #define EP_MAX_HS 9 #define EP_FIFO_SIZE_HS 4096 - #define USB_OTG_HS_PERIPH_BASE USB1_OTG_HS_BASE - #define OTG_HS_IRQn USB1_OTG_HS_IRQn + #define USB_OTG_FS_PERIPH_BASE USB1_OTG_HS_BASE + #define OTG_FS_IRQn USB1_OTG_HS_IRQn + + #define USB_OTG_HS_PERIPH_BASE USB2_OTG_HS_BASE + #define OTG_HS_IRQn USB2_OTG_HS_IRQn #elif CFG_TUSB_MCU == OPT_MCU_STM32F7 #include "stm32f7xx.h" @@ -261,6 +264,22 @@ static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { } } +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + if (hs_phy_type == GHWCFG2_HSPHY_NOT_SUPPORTED) { + // Disable on-chip FS PHY + dwc2->stm32_gccfg &= ~STM32_GCCFG_PWRDWN; + } else { + // Disable HS PHY + #ifdef USB_HS_PHYC + dwc2->stm32_gccfg &= ~STM32_GCCFG_PHYHSEN; + // Disable PLL and LDO + USB_HS_PHYC->USB_HS_PHYC_PLL &= ~USB_HS_PHYC_PLL_PLLEN; + USB_HS_PHYC->USB_HS_PHYC_LDO &= ~USB_HS_PHYC_LDO_ENABLE; + #endif + } +} + // MCU specific PHY update, it is called AFTER init() and core reset static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { // used to set turnaround time for fullspeed, nothing to do in highspeed mode @@ -300,6 +319,66 @@ static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { } } +//------------- GCCFG configuration -------------// +static inline void dwc2_stm32_gccfg_cfg(dwc2_regs_t* dwc2, bool vbus_sensing, bool is_host) { + if (is_host) { + vbus_sensing = false; + } + + uint32_t gccfg = dwc2->stm32_gccfg; + if (dwc2->guid < 0x2000) { + // use VBUSASEN/VBUSBSEN/NOVBUSSENS bits + if (is_host) { + gccfg &= ~(STM32_GCCFG_NOVBUSSENS | STM32_GCCFG_VBUSBSEN | STM32_GCCFG_VBUSASEN); + } else { + if (vbus_sensing) { + gccfg &= ~STM32_GCCFG_NOVBUSSENS; + gccfg |= STM32_GCCFG_VBUSBSEN; + } else { + gccfg |= STM32_GCCFG_NOVBUSSENS; + gccfg &= ~(STM32_GCCFG_VBUSBSEN | STM32_GCCFG_VBUSASEN); + } + } + } else if (dwc2->guid < 0x5000) { + // the later version uses VBDEN with battery charging detection + if (vbus_sensing) { + gccfg |= STM32_GCCFG_VBDEN; + } else { + gccfg &= ~STM32_GCCFG_VBDEN; + } + } else { + // from 0x5000 ST seems to use femtoPHY for UTMI+ HS PHY. Which use VBVALEXTOEN and VBVALOVAL for software override + // external VBUS sensing + // Note: N6 does not support hardware VBUS sensing, so the software override is always active. Therefore, VBDEN and + // VBVALEXTOEN are not available +#if CFG_TUSB_MCU == OPT_MCU_STM32N6 + if (is_host) { + gccfg |= STM32_GCCFG_PULLDOWNEN; + gccfg &= ~(STM32_GCCFG_VBVALOVAL); + } else { + gccfg &= ~STM32_GCCFG_PULLDOWNEN; + gccfg |= STM32_GCCFG_VBVALOVAL; + } +#else + if (is_host) { + gccfg |= STM32_GCCFG_PULLDOWNEN; + gccfg &= ~(STM32_GCCFG_VBDEN | STM32_GCCFG_VBVALEXTOEN | STM32_GCCFG_VBVALOVAL); + } else { + gccfg &= ~STM32_GCCFG_PULLDOWNEN; + if (vbus_sensing) { + gccfg |= STM32_GCCFG_VBDEN; + gccfg &= ~(STM32_GCCFG_VBVALEXTOEN | STM32_GCCFG_VBVALOVAL); + } else { + gccfg &= ~STM32_GCCFG_VBDEN; + gccfg |= STM32_GCCFG_VBVALEXTOEN | STM32_GCCFG_VBVALOVAL; + } + } +#endif + } + + dwc2->stm32_gccfg = gccfg; +} + //------------- DCache -------------// #if CFG_TUD_MEM_DCACHE_ENABLE || CFG_TUH_MEM_DCACHE_ENABLE @@ -321,8 +400,13 @@ static mem_region_t uncached_regions[] = { // DTCM (although USB DMA can't transfer to/from DTCM) {.start = 0x20000000, .end = 0x2002FFFF}, #elif CFG_TUSB_MCU == OPT_MCU_STM32F7 - // DTCM + // DTCM {.start = 0x20000000, .end = 0x2000FFFF}, +#elif CFG_TUSB_MCU == OPT_MCU_STM32N6 + // DTCM NS + {.start = 0x20000000, .end = 0x2003FFFF}, + // DTCM S + {.start = 0x30000000, .end = 0x3003FFFF}, #else #error "Cache maintenance is not supported yet" #endif diff --git a/src/portable/synopsys/dwc2/dwc2_type.h b/src/portable/synopsys/dwc2/dwc2_type.h index 7693ce02a..596bd0b34 100644 --- a/src/portable/synopsys/dwc2/dwc2_type.h +++ b/src/portable/synopsys/dwc2/dwc2_type.h @@ -1650,24 +1650,38 @@ TU_VERIFY_STATIC(offsetof(dwc2_regs_t, fifo ) == 0x1000, "incorrect size"); #define STM32_GCCFG_PHYHSEN_Msk (0x1UL << STM32_GCCFG_PHYHSEN_Pos) // 0x00800000 #define STM32_GCCFG_PHYHSEN STM32_GCCFG_PHYHSEN_Msk // HS PHY enable -// TODO stm32u5a5 SDEN is 22nd bit, conflict with 20th bit above -//#define STM32_GCCFG_SDEN_Pos (22U) -//#define STM32_GCCFG_SDEN_Msk (0x1U << STM32_GCCFG_SDEN_Pos) // 0x00400000 -//#define STM32_GCCFG_SDEN STM32_GCCFG_SDEN_Msk // Secondary detection (PD) mode enable +// GUID < 0x2000: VBUSASEN, VBUSBSEN, NOVBUSSENS bits +#define STM32_GCCFG_VBUSASEN_Pos (18U) +#define STM32_GCCFG_VBUSASEN_Msk (0x1UL << STM32_GCCFG_VBUSASEN_Pos) // 0x00040000 +#define STM32_GCCFG_VBUSASEN STM32_GCCFG_VBUSASEN_Msk // Enable A-device (host) VBUS sensing -// TODO stm32u5a5 VBVALOVA is 23rd bit, conflict with PHYHSEN bit above -#define STM32_GCCFG_VBVALOVAL_Pos (23U) -#define STM32_GCCFG_VBVALOVAL_Msk (0x1U << STM32_GCCFG_VBVALOVAL_Pos) // 0x00800000 -#define STM32_GCCFG_VBVALOVAL STM32_GCCFG_VBVALOVAL_Msk // Value of VBUSVLDEXT0 femtoPHY input +#define STM32_GCCFG_VBUSBSEN_Pos (19U) +#define STM32_GCCFG_VBUSBSEN_Msk (0x1UL << STM32_GCCFG_VBUSBSEN_Pos) // 0x00080000 +#define STM32_GCCFG_VBUSBSEN STM32_GCCFG_VBUSBSEN_Msk // Enable B-device (peripheral) VBUS sensing -#define STM32_GCCFG_VBVALEXTOEN_Pos (24U) -#define STM32_GCCFG_VBVALEXTOEN_Msk (0x1U << STM32_GCCFG_VBVALEXTOEN_Pos) // 0x01000000 -#define STM32_GCCFG_VBVALEXTOEN STM32_GCCFG_VBVALEXTOEN_Msk // Enables of VBUSVLDEXT0 femtoPHY input override +#define STM32_GCCFG_NOVBUSSENS_Pos (21U) +#define STM32_GCCFG_NOVBUSSENS_Msk (0x1UL << STM32_GCCFG_NOVBUSSENS_Pos) // 0x00200000 +#define STM32_GCCFG_NOVBUSSENS STM32_GCCFG_NOVBUSSENS_Msk // VBUS sensing disable option +// GUID < 0x2000: end -#define STM32_GCCFG_PULLDOWNEN_Pos (25U) -#define STM32_GCCFG_PULLDOWNEN_Msk (0x1U << STM32_GCCFG_PULLDOWNEN_Pos) // 0x02000000 -#define STM32_GCCFG_PULLDOWNEN STM32_GCCFG_PULLDOWNEN_Msk // Enables of femtoPHY pulldown resistors, used when ID PAD is disabled +// TODO: stm32u5a5 SDEN is 22nd bit, conflict with 20th bit above +// #define STM32_GCCFG_SDEN_Pos (22U) +// #define STM32_GCCFG_SDEN_Msk (0x1U << STM32_GCCFG_SDEN_Pos) // 0x00400000 +// #define STM32_GCCFG_SDEN STM32_GCCFG_SDEN_Msk // Secondary detection (PD) mode enable +// GUID >= 0x5000 use femtoPHY: VBVALOVA, VBVALEXTOEN, PULLDOWNEN +#define STM32_GCCFG_VBVALOVAL_Pos (23U) +#define STM32_GCCFG_VBVALOVAL_Msk (0x1U << STM32_GCCFG_VBVALOVAL_Pos) // 0x00800000 +#define STM32_GCCFG_VBVALOVAL STM32_GCCFG_VBVALOVAL_Msk // Value of VBUSVLDEXT0 femtoPHY input + +#define STM32_GCCFG_VBVALEXTOEN_Pos (24U) +#define STM32_GCCFG_VBVALEXTOEN_Msk (0x1U << STM32_GCCFG_VBVALEXTOEN_Pos) // 0x01000000 +#define STM32_GCCFG_VBVALEXTOEN STM32_GCCFG_VBVALEXTOEN_Msk // Enables of VBUSVLDEXT0 femtoPHY input override + +#define STM32_GCCFG_PULLDOWNEN_Pos (25U) +#define STM32_GCCFG_PULLDOWNEN_Msk (0x1U << STM32_GCCFG_PULLDOWNEN_Pos) // 0x02000000 +#define STM32_GCCFG_PULLDOWNEN STM32_GCCFG_PULLDOWNEN_Msk // Enables of femtoPHY pulldown resistors, used when ID PAD is disabled +// GUID >= 0x5000: end /******************** Bit definition for DEACHINTMSK register ********************/ #define DEACHINTMSK_IEP1INTM_Pos (1U) diff --git a/src/portable/synopsys/dwc2/dwc2_xmc.h b/src/portable/synopsys/dwc2/dwc2_xmc.h index 63419abf7..aca3873df 100644 --- a/src/portable/synopsys/dwc2/dwc2_xmc.h +++ b/src/portable/synopsys/dwc2/dwc2_xmc.h @@ -71,6 +71,13 @@ static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) //USB->ROUTE = USB_ROUTE_PHYPEN; } +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // nothing to do +} + // MCU specific PHY update, it is called AFTER init() and core reset static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c index c40703b09..e12e44a41 100644 --- a/src/portable/synopsys/dwc2/hcd_dwc2.c +++ b/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -111,7 +111,8 @@ typedef struct { hcd_endpoint_t edpt[CFG_TUH_DWC2_ENDPOINT_MAX]; } hcd_data_t; -hcd_data_t _hcd_data; +static hcd_data_t _hcd_data; +static tuh_configure_dwc2_t _tuh_cfg = {.use_hs_phy = TUH_OPT_HIGH_SPEED}; //-------------------------------------------------------------------- // @@ -350,7 +351,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t cal_next_pid(uint8_t pid, uint8_t pa * TX periodic (PTX) * - At least largest-EPsize*MulCount/4 (MulCount up to 3 for high-bandwidth ISO/interrupt) */ -static void dfifo_host_init(uint8_t rhport) { +static void dfifo_host_init(uint8_t rhport, bool is_hs_phy) { const dwc2_controller_t* dwc2_controller = &_dwc2_controller[rhport]; dwc2_regs_t* dwc2 = DWC2_REG(rhport); const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; @@ -363,10 +364,9 @@ static void dfifo_host_init(uint8_t rhport) { } // fixed allocation for now, improve later: - // - ptx_largest is limited to 256 for FS since most FS core only has 1024 bytes total - bool is_highspeed = dwc2_core_is_highspeed(dwc2, TUSB_ROLE_HOST); - uint32_t nptx_largest = is_highspeed ? TUSB_EPSIZE_BULK_HS/4 : TUSB_EPSIZE_BULK_FS/4; - uint32_t ptx_largest = is_highspeed ? TUSB_EPSIZE_ISO_HS_MAX/4 : 256/4; + // - ptx_largest is limited to 256 for FS since most FS core only has 1024 bytes total + uint32_t nptx_largest = is_hs_phy ? TUSB_EPSIZE_BULK_HS / 4 : TUSB_EPSIZE_BULK_FS / 4; + uint32_t ptx_largest = is_hs_phy ? TUSB_EPSIZE_ISO_HS_MAX / 4 : 256 / 4; uint16_t nptxfsiz = 2 * nptx_largest; uint16_t rxfsiz = 2 * (ptx_largest + 2) + ghwcfg2.num_host_ch; @@ -392,29 +392,23 @@ static void dfifo_host_init(uint8_t rhport) { // 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; - + TU_VERIFY(cfg_id == TUH_CFGID_DWC2 && cfg_param != NULL); + tuh_configure_param_t const* cfg = (tuh_configure_param_t const*) cfg_param; + _tuh_cfg = cfg->dwc2; return true; } // Initialize controller to host mode bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rh_init; dwc2_regs_t* dwc2 = DWC2_REG(rhport); - tu_memclr(&_hcd_data, sizeof(_hcd_data)); // Core Initialization - const bool is_highspeed = dwc2_core_is_highspeed(dwc2, TUSB_ROLE_HOST); + const bool is_hs_phy = dwc2_core_is_highspeed_phy(dwc2, _tuh_cfg.use_hs_phy); const bool is_dma = dma_host_enabled(dwc2); - TU_ASSERT(dwc2_core_init(rhport, is_highspeed, is_dma)); + TU_ASSERT(dwc2_core_init(rhport, is_hs_phy, is_dma)); //------------- 3.1 Host Initialization -------------// - - // work at max supported speed - dwc2->hcfg &= ~HCFG_FSLS_ONLY; - // Enable HFIR reload if (dwc2->gsnpsid >= DWC2_CORE_REV_2_92a) { dwc2->hfir |= HFIR_RELOAD_CTRL; @@ -422,20 +416,26 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { // force host mode and wait for mode switch dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_FDMOD) | GUSBCFG_FHMOD; -#if CFG_TUSB_MCU == OPT_MCU_STM32N6 - // No hardware detection of Vbus B-session is available on the STM32N6 - dwc2->stm32_gccfg &= ~STM32_GCCFG_VBVALOVAL; -#endif while ((dwc2->gintsts & GINTSTS_CMOD) != GINTSTS_CMODE_HOST) {} - // configure fixed-allocated fifo scheme - dfifo_host_init(rhport); + #ifdef TUP_USBIP_DWC2_STM32 + dwc2_stm32_gccfg_cfg(dwc2, false, true); + #endif + + if (is_hs_phy && (rh_init->speed == TUSB_SPEED_HIGH || rh_init->speed == TUSB_SPEED_AUTO)) { + dwc2->hcfg &= ~HCFG_FSLS_ONLY; // max speed + } else { + dwc2->hcfg |= HCFG_FSLS_ONLY; // disable high speed mode + } + + // configure a fixed-allocated fifo scheme + dfifo_host_init(rhport, is_hs_phy); dwc2->hprt = HPRT_W1_MASK; // clear all write-1-clear bits dwc2->hprt = HPRT_POWER; // turn on VBUS // Enable required interrupts - dwc2->gintmsk |= GINTSTS_OTGINT | GINTSTS_CONIDSTSCHNG | GINTSTS_HPRTINT | GINTSTS_HCINT | GINTSTS_DISCINT; + dwc2->gintmsk |= GINTSTS_OTGINT | GINTSTS_HPRTINT | GINTSTS_HCINT | GINTSTS_DISCINT; // NPTX can hold at least 2 packet, change interrupt level to half-empty uint32_t gahbcfg = dwc2->gahbcfg & ~GAHBCFG_TX_FIFO_EPMTY_LVL; @@ -445,6 +445,17 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { return true; } +bool hcd_deinit(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + // Turn off VBUS + dwc2->hprt = HPRT_W1_MASK; // clear w1c bits without side effects + // HPRT_POWER is not set -> VBUS off + + dwc2_core_deinit(rhport); + return true; +} + // Enable USB interrupt void hcd_int_enable (uint8_t rhport) { dwc2_int_set(rhport, TUSB_ROLE_HOST, true); @@ -869,7 +880,7 @@ static void handle_rxflvl_irq(uint8_t rhport) { break; case GRXSTS_PKTSTS_HOST_DATATOGGLE_ERR: - TU_ASSERT(0, ); // maybe try to change DToggle + // handle in channel interrupt break; case GRXSTS_PKTSTS_HOST_CHANNEL_HALTED: @@ -1009,8 +1020,11 @@ static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t h channel_xfer_in_retry(dwc2, ch_id, hcint); } } else if (hcint & HCINT_DATATOGGLE_ERR) { + channel->hcintmsk &= ~HCINT_DATATOGGLE_ERR; xfer->err_count = 0; - TU_ASSERT(false); + hcsplt.split_compl = 0; // restart with start-split + channel->hcsplt = hcsplt.value; + channel_disable(dwc2, channel); } else { // nothing to do } @@ -1346,6 +1360,17 @@ static bool handle_sof_irq(uint8_t rhport, bool in_isr) { } // Config HCFG FS/LS clock and HFIR for SOF interval according to link speed (value is in PHY clock unit) +// Databook Table 2-2: System Clock Speeds +// +-----------+------------------+----------+-----------+-------------------+ +// | PHY | PHY Clock (MHz) | Width | HCFG.Sel | HFIR (clk cycles) | +// +-----------+------------------+----------+-----------+-------------------+ +// | HS UTMI+ | 30 | 16-bit | 30_60 | HS:3749 FS:29999 | +// | HS UTMI+ | 60 | 8-bit | 30_60 | HS:7499 FS:59999 | +// | HS ULPI | 60 | 8-bit | 30_60 | HS:7499 FS:59999 | +// | FS (dead.) | 48 | internal | 48 | FS:47999 | +// | LS via FS | 48 (6 effective) | internal | 6 | LS:47999 | +// +-----------+------------------+----------+-----------+-------------------+ +// HFIR = (interval_us * phy_clock) - 1, where interval is 125us (HS) or 1000us (FS/LS) static void port0_enable(dwc2_regs_t* dwc2, tusb_speed_t speed) { uint32_t hcfg = dwc2->hcfg & ~HCFG_FSLS_PHYCLK_SEL; @@ -1438,16 +1463,6 @@ void hcd_int_handler(uint8_t rhport, bool in_isr) { // TU_LOG1_HEX(gintsts); - if (gintsts & GINTSTS_CONIDSTSCHNG) { - // Connector ID status change - dwc2->gintsts = GINTSTS_CONIDSTSCHNG; - - //if (dwc2->gotgctl) - // dwc2->hprt = HPRT_POWER; // power on port to turn on VBUS - //dwc2->gintmsk |= GINTMSK_PRTIM; - // TODO wait for SRP if OTG - } - if (gintsts & GINTSTS_SOF) { const bool more_sof = handle_sof_irq(rhport, in_isr); if (!more_sof) { diff --git a/src/tinyusb.mk b/src/tinyusb.mk index 8f9d52de9..e7d1c0b5b 100644 --- a/src/tinyusb.mk +++ b/src/tinyusb.mk @@ -15,6 +15,7 @@ TINYUSB_SRC_C += \ src/class/mtp/mtp_device.c \ src/class/net/ecm_rndis_device.c \ src/class/net/ncm_device.c \ + src/class/printer/printer_device.c \ src/class/usbtmc/usbtmc_device.c \ src/class/video/video_device.c \ src/class/vendor/vendor_device.c \ diff --git a/src/tusb.c b/src/tusb.c index ed254a10b..4ea396715 100644 --- a/src/tusb.c +++ b/src/tusb.c @@ -39,23 +39,38 @@ #include "host/usbh_pvt.h" #endif +// Suppress IAR warning +// Warning[Pe111]: statement is unreachable +#if defined(__ICCARM__) +#pragma diag_suppress = Pe111 +#endif + tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM] = { TUSB_ROLE_INVALID }; //-------------------------------------------------------------------- // Weak/Default API, can be overwritten by Application //-------------------------------------------------------------------- -TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) { #if CFG_TUSB_OS != OPT_OS_NONE +TU_ATTR_WEAK uint32_t tusb_time_millis_api(void) { + return osal_time_millis(); +} + +TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) { osal_task_delay(ms); +} + #else - // delay using millis() (if implemented) and/or frame number if possible +// tusb_time_millis_api() must be implemented by user application. + +TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) { + // delay using millis() const uint32_t time_ms = tusb_time_millis_api(); while ((tusb_time_millis_api() - time_ms) < ms) {} -#endif } +#endif -TU_ATTR_WEAK void* tusb_app_virt_to_phys(void *virt_addr) { +TU_ATTR_WEAK void *tusb_app_virt_to_phys(void *virt_addr) { return virt_addr; } @@ -245,6 +260,7 @@ bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex) { bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed) { const uint16_t max_packet_size = tu_edpt_packet_size(desc_ep); TU_LOG2(" Open EP %02X with Size = %u\r\n", desc_ep->bEndpointAddress, max_packet_size); + TU_ASSERT(max_packet_size > 0); switch (desc_ep->bmAttributes.xfer) { case TUSB_XFER_ISOCHRONOUS: { @@ -264,7 +280,7 @@ bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed) { break; case TUSB_XFER_INTERRUPT: { - uint16_t const spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 64); + const uint16_t spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 64); TU_ASSERT(max_packet_size <= spec_size); break; } @@ -309,11 +325,9 @@ bool tu_edpt_stream_init(tu_edpt_stream_t *s, bool is_host, bool is_tx, bool ove uint16_t ff_bufsize, uint8_t *ep_buf, uint16_t ep_bufsize) { (void) is_tx; - #if CFG_TUSB_EDPT_STREAM_NO_FIFO_ENABLED == 0 // FIFO is required if (ff_buf == NULL || ff_bufsize == 0) { return false; } - #endif s->is_host = is_host; tu_fifo_config(&s->ff, ff_buf, ff_bufsize, overwritable); @@ -394,7 +408,7 @@ uint32_t tu_edpt_stream_write_xfer(tu_edpt_stream_t *s) { // Pull data from FIFO -> EP buf uint16_t count; if (s->ep_buf == NULL) { - count = ff_count; + count = tu_fifo_count(&s->ff); // re-get count since fifo can be changed } else { count = tu_fifo_read_n(&s->ff, s->ep_buf, s->ep_bufsize); } @@ -411,90 +425,45 @@ uint32_t tu_edpt_stream_write_xfer(tu_edpt_stream_t *s) { } uint32_t tu_edpt_stream_write(tu_edpt_stream_t *s, const void *buffer, uint32_t bufsize) { - #if CFG_TUSB_EDPT_STREAM_NO_FIFO_ENABLED - if (0 == tu_fifo_depth(&s->ff)) { - // non-fifo mode: TX need ep buffer - TU_VERIFY(s->ep_buf != NULL, 0); - TU_VERIFY(stream_claim(s), 0); - uint32_t xact_len = tu_min32(bufsize, s->ep_bufsize); - memcpy(s->ep_buf, buffer, xact_len); - TU_ASSERT(stream_xfer(s, (uint16_t) xact_len), 0); - return xact_len; - } else - #endif - { - TU_VERIFY(bufsize > 0); - const uint16_t ret = tu_fifo_write_n(&s->ff, buffer, (uint16_t) bufsize); + TU_VERIFY(bufsize > 0); + const uint16_t ret = tu_fifo_write_n(&s->ff, buffer, (uint16_t) bufsize); - // flush if fifo has more than packet size or - // in rare case: fifo depth is configured too small (which never reach packet size) - if ((tu_fifo_count(&s->ff) >= s->mps) || (tu_fifo_depth(&s->ff) < s->mps)) { - tu_edpt_stream_write_xfer(s); - } - return ret; + // flush if fifo has more than packet size or + // in rare case: fifo depth is configured too small (which never reach packet size) + if ((tu_fifo_count(&s->ff) >= s->mps) || (tu_fifo_depth(&s->ff) < s->mps)) { + tu_edpt_stream_write_xfer(s); } + return ret; } uint32_t tu_edpt_stream_write_available(tu_edpt_stream_t *s) { - #if CFG_TUSB_EDPT_STREAM_NO_FIFO_ENABLED - if (0 == tu_fifo_depth(&s->ff)) { - // non-fifo mode - TU_VERIFY(s->ep_addr > 0); // must be opened - bool is_busy = true; - if (s->is_host) { - #if CFG_TUH_ENABLED - is_busy = usbh_edpt_busy(s->hwid, s->ep_addr); - #endif - } else { - #if CFG_TUD_ENABLED - is_busy = usbd_edpt_busy(s->hwid, s->ep_addr); - #endif - } - return is_busy ? 0 : s->ep_bufsize; - } else - #endif - { - return (uint32_t)tu_fifo_remaining(&s->ff); - } + return (uint32_t)tu_fifo_remaining(&s->ff); } //--------------------------------------------------------------------+ // Stream Read //--------------------------------------------------------------------+ uint32_t tu_edpt_stream_read_xfer(tu_edpt_stream_t *s) { - #if CFG_TUSB_EDPT_STREAM_NO_FIFO_ENABLED - if (0 == tu_fifo_depth(&s->ff)) { - // non-fifo mode: RX need ep buffer - TU_VERIFY(s->ep_buf != NULL, 0); - TU_VERIFY(stream_claim(s), 0); - TU_ASSERT(stream_xfer(s, s->ep_bufsize), 0); - return s->ep_bufsize; - } else - #endif - { - uint16_t available = tu_fifo_remaining(&s->ff); + uint16_t available = tu_fifo_remaining(&s->ff); - // Prepare for incoming data but only allow what we can store in the ring buffer. - // TODO Actually we can still carry out the transfer, keeping count of received bytes - // and slowly move it to the FIFO when read(). - // This pre-check reduces endpoint claiming - TU_VERIFY(available >= s->mps); - TU_VERIFY(stream_claim(s), 0); - available = tu_fifo_remaining(&s->ff); // re-get available since fifo can be changed + // Prepare for incoming data but only allow what we can store in the ring buffer. + // TODO Actually we can still carry out the transfer, keeping count of received bytes + // and slowly move it to the FIFO when read(). + // This pre-check reduces endpoint claiming + TU_VERIFY(available >= s->mps); + TU_VERIFY(stream_claim(s), 0); + available = tu_fifo_remaining(&s->ff); // re-get available since fifo can be changed - if (available >= s->mps) { - // multiple of packet size limit by ep bufsize - uint16_t count = (uint16_t) (available & ~(s->mps - 1)); - if (s->ep_buf != NULL) { - count = tu_min16(count, s->ep_bufsize); - } - TU_ASSERT(stream_xfer(s, count), 0); - return count; - } else { - // Release endpoint since we don't make any transfer - stream_release(s); - return 0; - } + if (available >= s->mps) { + // multiple of packet size limit by ep bufsize + uint16_t count = (uint16_t) (available & ~(s->mps - 1)); + count = tu_min16(count, s->ep_bufsize); + TU_ASSERT(stream_xfer(s, count), 0); + return count; + } else { + // Release endpoint since we don't make any transfer + stream_release(s); + return 0; } } diff --git a/src/tusb.h b/src/tusb.h index 62b3b9783..c80c8433c 100644 --- a/src/tusb.h +++ b/src/tusb.h @@ -88,6 +88,10 @@ #include "class/msc/msc_device.h" #endif + #if CFG_TUD_PRINTER + #include "class/printer/printer_device.h" + #endif + #if CFG_TUD_MTP #include "class/mtp/mtp_device.h" #endif @@ -135,7 +139,7 @@ //--------------------------------------------------------------------+ -// User API +// Application API //--------------------------------------------------------------------+ #if CFG_TUH_ENABLED || CFG_TUD_ENABLED diff --git a/src/tusb_option.h b/src/tusb_option.h index 08a0ba2ef..f5879e1df 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -216,6 +216,7 @@ #define OPT_MCU_AT32F402_405 2504 ///< ArteryTek AT32F402_405 #define OPT_MCU_AT32F425 2505 ///< ArteryTek AT32F425 #define OPT_MCU_AT32F413 2506 ///< ArteryTek AT32F413 +#define OPT_MCU_AT32F45X 2507 ///< ArteryTek AT32F45x // HPMicro #define OPT_MCU_HPM 2600 ///< HPMicro @@ -237,6 +238,7 @@ #define OPT_OS_RTTHREAD 6 ///< RT-Thread #define OPT_OS_RTX4 7 ///< Keil RTX 4 #define OPT_OS_ZEPHYR 8 ///< Zephyr +#define OPT_OS_THREADX 9 ///< ThreadX //--------------------------------------------------------------------+ // Mode and Speed @@ -271,6 +273,25 @@ // USBIP //--------------------------------------------------------------------+ +//------------- ChipIdea -------------// +// Enable CI_HS VBUS Charge. Set this to 1 if the USB_VBUS pin is not connected to 5V VBUS (note: 3.3V is +// insufficient). +#ifndef CFG_TUD_CI_HS_VBUS_CHARGE + #ifndef CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT + #define CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT 0 + #endif + #define CFG_TUD_CI_HS_VBUS_CHARGE CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT +#endif + +// CI_HS support FIFO transfer if endpoint buffer is 4k aligned and size is multiple of 4k, also DCACHE is disabled +#ifndef CFG_TUD_CI_HS_EPBUF_4K_ALIGNED + #define CFG_TUD_CI_HS_EPBUF_4K_ALIGNED 0 +#endif + +#if CFG_TUD_CI_HS_EPBUF_4K_ALIGNED && !CFG_TUD_MEM_DCACHE_ENABLE + #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 +#endif + //------------- DWC2 -------------// // DMA mode for device #ifndef CFG_TUD_DWC2_DMA_ENABLE @@ -316,57 +337,28 @@ #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 // fixed hwfifo address #endif -//------------- ChipIdea -------------// -// Enable CI_HS VBUS Charge. Set this to 1 if the USB_VBUS pin is not connected to 5V VBUS (note: 3.3V is -// insufficient). -#ifndef CFG_TUD_CI_HS_VBUS_CHARGE - #ifndef CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT - #define CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT 0 - #endif - #define CFG_TUD_CI_HS_VBUS_CHARGE CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT -#endif - -// CI_HS support FIFO transfer if endpoint buffer is 4k aligned and size is multiple of 4k, also DCACHE is disabled -#ifndef CFG_TUD_CI_HS_EPBUF_4K_ALIGNED - #define CFG_TUD_CI_HS_EPBUF_4K_ALIGNED 0 -#endif - -#if CFG_TUD_CI_HS_EPBUF_4K_ALIGNED && !CFG_TUD_MEM_DCACHE_ENABLE - #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 -#endif - -//------------- pio-usb -------------// -// Enable PIO-USB software host controller -#ifndef CFG_TUH_RPI_PIO_USB - #define CFG_TUH_RPI_PIO_USB 0 -#endif - -#ifndef CFG_TUD_RPI_PIO_USB - #define CFG_TUD_RPI_PIO_USB 0 -#endif - -//------------ MAX3421 -------------// -// Enable MAX3421 USB host controller -#ifndef CFG_TUH_MAX3421 - #define CFG_TUH_MAX3421 0 -#endif - //------------ FSDEV --------------// #if defined(TUP_USBIP_FSDEV) #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 - #if CFG_TUSB_FSDEV_PMA_SIZE == 512 - #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 2 // 16-bit data + #if CFG_TUSB_FSDEV_PMA_SIZE == 2048 || TU_CHECK_MCU(OPT_MCU_STM32U0) + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 // 32-bit data #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 4 // 32-bit address increase #elif CFG_TUSB_FSDEV_PMA_SIZE == 1024 #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 2 // 16-bit data #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 2 // 16-bit address increase - #elif CFG_TUSB_FSDEV_PMA_SIZE == 2048 - #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 // 32-bit data + #elif CFG_TUSB_FSDEV_PMA_SIZE == 512 + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 2 // 16-bit data #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 4 // 32-bit address increase #endif #endif +//------------ MAX3421 -------------// +// Enable MAX3421 USB host controller +#ifndef CFG_TUH_MAX3421 + #define CFG_TUH_MAX3421 0 +#endif + //------------ MUSB --------------// #if defined(TUP_USBIP_MUSB) #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 @@ -374,18 +366,53 @@ #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS // allow odd 16bit access #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS // allow odd 8bit access #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 // fixed hwfifo +#endif + +//------------- Raspberry Pi -------------// +// Enable PIO-USB software host controller +#ifndef CFG_TUH_RPI_PIO_USB + #define CFG_TUH_RPI_PIO_USB 0 +#endif + +#ifndef CFG_TUD_RPI_PIO_USB + #define CFG_TUD_RPI_PIO_USB 0 +#endif +#if (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUD_RPI_PIO_USB + #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 1 + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 1 + #define CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE + #define CFG_TUSB_FIFO_HWFIFO_CUSTOM_READ #endif //------------ RUSB2 --------------// #if defined(TUP_USBIP_RUSB2) #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 - #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE (2 | (TUD_OPT_HIGH_SPEED ? 4 : 0)) // 16 bit and 32 bit data if highspeed + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE (2 | (TUD_OPT_HIGH_SPEED ? 4 : 0)) // 16 bit and 32 bit if highspeed #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 #define CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE // custom write since rusb2 can change access width 32 -> 16 and can write // odd byte with byte access #endif +//------- Microchip SAMX7X -------// +// DMA mode for device +#ifndef CFG_TUD_SAMX7X_DMA_ENABLE + #ifndef CFG_TUD_SAMX7X_DMA_ENABLE_DEFAULT + #define CFG_TUD_SAMX7X_DMA_ENABLE_DEFAULT 0 + #endif + + #define CFG_TUD_SAMX7X_DMA_ENABLE CFG_TUD_SAMX7X_DMA_ENABLE_DEFAULT +#endif + +#if (CFG_TUSB_MCU == OPT_MCU_SAMX7X) + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 4 + #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS + #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS + #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 +#endif + //-------------------------------------------------------------------- // RootHub Mode detection //-------------------------------------------------------------------- @@ -461,7 +488,6 @@ #define TUP_MCU_STRICT_ALIGN 0 #endif - //--------------------------------------------------------------------+ // Common Options (Default) //--------------------------------------------------------------------+ @@ -506,6 +532,10 @@ #define CFG_TUSB_OS OPT_OS_NONE #endif +#ifndef CFG_TUSB_OS_HAS_SCHEDULER + #define CFG_TUSB_OS_HAS_SCHEDULER (CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO) +#endif + #ifndef CFG_TUSB_OS_INC_PATH #ifndef CFG_TUSB_OS_INC_PATH_DEFAULT #define CFG_TUSB_OS_INC_PATH_DEFAULT @@ -552,6 +582,11 @@ #define CFG_TUD_INTERFACE_MAX 16 #endif +// max events processed in one tud_task_ext() call, 0 for unlimited +#ifndef CFG_TUD_TASK_EVENTS_PER_RUN + #define CFG_TUD_TASK_EVENTS_PER_RUN 16 +#endif + // default to max hardware endpoint, but can be smaller to save RAM #ifndef CFG_TUD_ENDPPOINT_MAX #define CFG_TUD_ENDPPOINT_MAX TUP_DCD_ENDPOINT_MAX @@ -566,9 +601,18 @@ #define CFG_TUD_TEST_MODE 0 #endif +#ifndef CFG_TUD_VBUS_DETECT_HW_DEFAULT + #define CFG_TUD_VBUS_DETECT_HW_DEFAULT 0 +#endif + +// Enable VBUS Detect hardware, usually via functional GPIO +#ifndef CFG_TUD_VBUS_DETECT_HW + #define CFG_TUD_VBUS_DETECT_HW CFG_TUD_VBUS_DETECT_HW_DEFAULT +#endif + //------------- Device Class Driver -------------// #ifndef CFG_TUD_BTH - #define CFG_TUD_BTH 0 + #define CFG_TUD_BTH 0 #endif #if CFG_TUD_BTH && !defined(CFG_TUD_BTH_ISO_ALT_COUNT) @@ -632,6 +676,10 @@ #define CFG_TUD_NCM 0 #endif +#ifndef CFG_TUD_PRINTER + #define CFG_TUD_PRINTER 0 +#endif + #ifndef CFG_TUD_EDPT_DEDICATED_HWFIFO #define CFG_TUD_EDPT_DEDICATED_HWFIFO 0 #endif @@ -671,6 +719,11 @@ #define CFG_TUH_MEM_DCACHE_LINE_SIZE CFG_TUSB_MEM_DCACHE_LINE_SIZE #endif +// max events processed in one tuh_task_ext() call, 0 for unlimited +#ifndef CFG_TUH_TASK_EVENTS_PER_RUN + #define CFG_TUH_TASK_EVENTS_PER_RUN 16 +#endif + //------------- CLASS -------------// #ifndef CFG_TUH_HUB @@ -742,7 +795,7 @@ #define CFG_TUH_CDC_PL2303 0 #endif -#ifndef CFG_TUH_CDC_PL2303_VID_PID_QUIRKS_LIST +#ifndef CFG_TUH_CDC_PL2303_VID_PID_LIST // List of product IDs that can use the PL2303 CDC driver #define CFG_TUH_CDC_PL2303_VID_PID_LIST \ { 0x067b, 0x2303 }, /* initial 2303 */ \ |
