/* * eXtensible USB Device driver (XDCI) for Tegra X1 * * Copyright (c) 2020-2022 CTCaer * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define XUSB_TRB_SLOTS 16 //! TODO: Consider upping it. #define XUSB_LINK_TRB_IDX (XUSB_TRB_SLOTS - 1) #define XUSB_LAST_TRB_IDX (XUSB_TRB_SLOTS - 1) #define EP_DONT_RING 0 #define EP_RING_DOORBELL 1 typedef enum { XUSB_FULL_SPEED = 1, XUSB_HIGH_SPEED = 3, XUSB_SUPER_SPEED = 4 } xusb_speed_t; typedef enum { EP_DISABLED = 0, EP_RUNNING = 1, EP_HALTED = 2, EP_STOPPED = 3, EP_ERROR = 4 } xusb_ep_status_t; typedef enum { EP_TYPE_ISOC_OUT = 1, EP_TYPE_BULK_OUT = 2, EP_TYPE_INTR_OUT = 3, EP_TYPE_CNTRL = 4, EP_TYPE_ISOC_IN = 5, EP_TYPE_BULK_IN = 6, EP_TYPE_INTR_IN = 7 } xusb_ep_type_t; typedef enum { XUSB_DEFAULT = 0, XUSB_ADDRESSED_STS_WAIT = 1, XUSB_ADDRESSED = 2, XUSB_CONFIGURED_STS_WAIT = 3, XUSB_CONFIGURED = 4, XUSB_LUN_CONFIGURED_STS_WAIT = 5, XUSB_LUN_CONFIGURED = 6, XUSB_HID_CONFIGURED_STS_WAIT = 7, XUSB_HID_CONFIGURED = 8, // XUSB_CONNECTED = , // XUSB_DISCONNECTED = , // XUSB_RESET = , // XUSB_SUSPENDED = , } xusb_dev_state_t; typedef enum { XUSB_TRB_NONE = 0, XUSB_TRB_NORMAL = 1, XUSB_TRB_DATA = 3, XUSB_TRB_STATUS = 4, XUSB_TRB_LINK = 6, XUSB_TRB_TRANSFER = 32, XUSB_TRB_PORT_CHANGE = 34, XUSB_TRB_SETUP = 63, } xusb_trb_type_t; typedef enum { XUSB_COMP_INVALID = 0, XUSB_COMP_SUCCESS = 1, XUSB_COMP_DATA_BUFFER_ERROR = 2, XUSB_COMP_BABBLE_DETECTED_ERROR = 3, XUSB_COMP_USB_TRANSACTION_ERROR = 4, XUSB_COMP_TRB_ERROR = 5, XUSB_COMP_STALL_ERROR = 6, XUSB_COMP_RESOURCE_ERROR = 7, XUSB_COMP_BANDWIDTH_ERROR = 8, XUSB_COMP_NO_SLOTS_AVAILABLE_ERROR = 9, XUSB_COMP_INVALID_STREAM_TYPE_ERROR = 10, XUSB_COMP_SLOT_NOT_ENABLED_ERROR = 11, XUSB_COMP_EP_DISABLED_ERROR = 12, XUSB_COMP_SHORT_PKT = 13, XUSB_COMP_RING_UNDERRUN = 14, XUSB_COMP_RING_OVERRUN = 15, XUSB_COMP_VF_EVENT_RING_FULL_ERROR = 16, XUSB_COMP_PARAMETER_ERROR = 17, XUSB_COMP_BANDWIDTH_OVERRUN_ERROR = 18, XUSB_COMP_CONTEXT_STATE_ERROR = 19, XUSB_COMP_NO_PING_RESPONSE_ERROR = 20, XUSB_COMP_EVENT_RING_FULL_ERROR = 21, XUSB_COMP_INCOMPATIBLE_DEVICE_ERROR = 22, XUSB_COMP_MISSED_SERVICE_ERROR = 23, XUSB_COMP_COMMAND_RING_STOPPED = 24, XUSB_COMP_COMMAND_ABORTED = 25, XUSB_COMP_STOPPED = 26, XUSB_COMP_STOPPED_LENGTH_INVALID = 27, XUSB_COMP_STOPPED_SHORT_PACKET = 28, XUSB_COMP_EXIT_LATENCY_LARGE_ERROR = 29, XUSB_COMP_ISOCH_BUFFER_OVERRUN = 31, XUSB_COMP_EVENT_LOST_ERROR = 32, XUSB_COMP_UNDEFINED_ERROR = 33, XUSB_COMP_INVALID_STREAM_ID_ERROR = 34, XUSB_COMP_SECONDARY_BANDWIDTH_ERROR = 35, XUSB_COMP_SPLIT_TRANSACTION_ERROR = 36, XUSB_COMP_CODE_STREAM_NUMP_ERROR = 219, XUSB_COMP_PRIME_PIPE_RECEIVED = 220, XUSB_COMP_HOST_REJECTED = 221, XUSB_COMP_CTRL_DIR_ERROR = 222, XUSB_COMP_CTRL_SEQ_NUM_ERROR = 223 } xusb_comp_code_t; typedef struct _event_trb_t { u32 rsvd0; u32 rsvd1; u32 rsvd2:24; u32 comp_code:8; u32 cycle:1; u32 rsvd3:9; u32 trb_type:6; u32 ep_id:5; u32 rsvd4:11; } event_trb_t; typedef struct _transfer_event_trb_t { u32 trb_pointer_lo; u32 trb_pointer_hi; u32 trb_tx_len:24; u32 comp_code:8; u32 cycle:1; u32 rsvddw3_0:1; u32 event_data:1; u32 rsvddw3_1:7; u32 trb_type:6; u32 ep_id:5; u32 rsvddw3_2:11; } transfer_event_trb_t; typedef struct _setup_event_trb_t { usb_ctrl_setup_t ctrl_setup_data; u32 ctrl_seq_num:16; u32 rsvddw2_0:8; u32 comp_code:8; u32 cycle:1; u32 rsvddw3_0:9; u32 trb_type:6; u32 ep_id:5; u32 rsvddw3_1:11; } setup_event_trb_t; typedef struct _status_trb_t { u32 rsvd0; u32 rsvd1; u32 rsvd2:22; u32 interrupt_target:10; u32 cycle:1; u32 ent:1; u32 rsvd3_0:2; u32 chain:1; u32 ioc:1; u32 rsvd3_1:4; u32 trb_type:6; u32 dir:1; u32 rsvd3_2:15; } status_trb_t; typedef struct _normal_trb_t { u32 databufptr_lo; u32 databufptr_hi; u32 trb_tx_len:17; u32 td_size:5; u32 interrupt_target:10; u32 cycle:1; u32 ent:1; u32 isp:1; u32 no_snoop:1; u32 chain:1; u32 ioc:1; u32 idt:1; u32 rsvd0_0:2; u32 bei:1; u32 trb_type:6; u32 rsvd0_1:16; } normal_trb_t; typedef struct _data_trb_t { u32 databufptr_lo; u32 databufptr_hi; u32 trb_tx_len:17; u32 td_size:5; u32 interrupt_target:10; u32 cycle:1; u32 ent:1; u32 isp:1; u32 no_snoop:1; u32 chain:1; u32 ioc:1; u32 rsvd0_0:4; u32 trb_type:6; u32 dir:1; u32 rsvd0_1:15; } data_trb_t; typedef struct _link_trb_t { u32 rsvd0_0:4; u32 ring_seg_ptrlo:28; u32 ring_seg_ptrhi; u32 rsvd1_0:22; u32 interrupt_target:10; u32 cycle:1; u32 toggle_cycle:1; u32 rsvd3_0:2; u32 chain:1; u32 ioc:1; u32 rsvd3_1:4; u32 trb_type:6; u32 rsvd3_2:16; } link_trb_t; typedef struct _xusb_ep_ctx_t { // Common context. u32 ep_state:3; u32 rsvddW0_0:5; u32 mult:2; u32 max_pstreams:5; u32 lsa:1; u32 interval:8; u32 rsvddW0_1:8; u32 rsvddw1_0:1; u32 cerr:2; u32 ep_type:3; u32 rsvddw1_1:1; u32 hid:1; u32 max_burst_size:8; u32 max_packet_size:16; u32 dcs:1; u32 rsvddw2_0:3; u32 trd_dequeueptr_lo:28; u32 trd_dequeueptr_hi; u32 avg_trb_len:16; u32 max_esit_payload:16; // Nvidia context. u32 event_data_txlen_acc; u32 cprog:8; u32 sbyte:7; u32 tp:2; u32 rec:1; u32 cec:2; u32 ced:1; u32 hsp1:1; u32 rty1:1; u32 std:1; u32 status:8; u32 data_offset; u32 scratch_pad0; u32 scratch_pad1; u32 cping:8; u32 sping:8; u32 toggle_cycle:2; u32 no_snoop:1; u32 ro:1; u32 tlm:1; u32 dlm:1; u32 hsp2:1; u32 rty2:1; u32 stop_rec_req:8; u32 device_addr:8; u32 hub_addr:8; u32 root_port_num:8; u32 slot_id:8; u32 routing_string:20; u32 speed:4; u32 lpu:1; u32 mtt:1; u32 hub:1; u32 dci:5; u32 tthub_slot_id:8; u32 ttport_num:8; u32 ssf:4; u32 sps:2; u32 interrupt_target:10; u32 frz:1; u32 end:1; u32 elm:1; u32 mrx:1; u32 ep_linklo:28; u32 ep_linkhi; } xusb_ep_ctx_t; typedef struct _xusbd_controller_t { data_trb_t *cntrl_epenqueue_ptr; data_trb_t *cntrl_epdequeue_ptr; u32 cntrl_producer_cycle; data_trb_t *bulkout_epenqueue_ptr; data_trb_t *bulkout_epdequeue_ptr; u32 bulkout_producer_cycle; data_trb_t *bulkin_epenqueue_ptr; data_trb_t *bulkin_epdequeue_ptr; u32 bulkin_producer_cycle; event_trb_t *event_enqueue_ptr; event_trb_t *event_dequeue_ptr; u32 event_ccs; u32 device_state; u32 tx_bytes[2]; u32 tx_count[2]; u32 ctrl_seq_num; u32 config_num; u32 interface_num; u32 wait_for_event_trb; u32 port_speed; usb_desc_t *desc; usb_gadget_type gadget; u8 max_lun; bool max_lun_set; bool bulk_reset_req; } xusbd_controller_t; extern u32 hid_report_descriptor_jc_size; extern u32 hid_report_descriptor_touch_size; extern u8 hid_report_descriptor_jc[]; extern u8 hid_report_descriptor_touch[]; extern usb_desc_t usb_gadget_hid_jc_descriptors; extern usb_desc_t usb_gadget_hid_touch_descriptors; extern usb_desc_t usb_gadget_ums_descriptors; // All rings and EP context must be aligned to 0x10. typedef struct _xusbd_event_queues_t { event_trb_t xusb_event_ring_seg0[XUSB_TRB_SLOTS]; event_trb_t xusb_event_ring_seg1[XUSB_TRB_SLOTS]; data_trb_t xusb_cntrl_event_queue[XUSB_TRB_SLOTS]; data_trb_t xusb_bulkin_event_queue[XUSB_TRB_SLOTS]; data_trb_t xusb_bulkout_event_queue[XUSB_TRB_SLOTS]; volatile xusb_ep_ctx_t xusb_ep_ctxt[4]; } xusbd_event_queues_t; // Set event queues context to a 0x10 aligned address. xusbd_event_queues_t *xusb_evtq = (xusbd_event_queues_t *)XUSB_RING_ADDR; xusbd_controller_t *usbd_xotg; xusbd_controller_t usbd_xotg_controller_ctxt; static int _xusb_xhci_mask_wait(u32 reg, u32 mask, u32 val, u32 retries) { do { if ((XUSB_DEV_XHCI(reg) & mask) == val) return USB_RES_OK; usleep(1); --retries; } while (retries); return USB_ERROR_TIMEOUT; } // Event rings aligned to 0x10 static void _xusbd_ep_init_event_ring() { memset(xusb_evtq->xusb_event_ring_seg0, 0, sizeof(xusb_evtq->xusb_event_ring_seg0)); memset(xusb_evtq->xusb_event_ring_seg1, 0, sizeof(xusb_evtq->xusb_event_ring_seg1)); //! TODO USB3: enable pcie regulators. // Set Event Ring Segment 0 Base Address. XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERST0BALO) = (u32)xusb_evtq->xusb_event_ring_seg0; XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERST0BAHI) = 0; // Set Event Ring Segment 1 Base Address. XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERST1BALO) = (u32)xusb_evtq->xusb_event_ring_seg1; XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERST1BAHI) = 0; // Set Event Ring Segment sizes. XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERSTSZ) = (XUSB_TRB_SLOTS << 16) | XUSB_TRB_SLOTS; // Set Enqueue and Dequeue pointers. usbd_xotg->event_enqueue_ptr = xusb_evtq->xusb_event_ring_seg0; usbd_xotg->event_dequeue_ptr = xusb_evtq->xusb_event_ring_seg0; usbd_xotg->event_ccs = 1; // Event Ring Enqueue Pointer. u32 evt_ring_addr = (u32)xusb_evtq->xusb_event_ring_seg0 & 0xFFFFFFF0; XUSB_DEV_XHCI(XUSB_DEV_XHCI_EREPLO) = (XUSB_DEV_XHCI(XUSB_DEV_XHCI_EREPLO) & 0xE) | evt_ring_addr | XCHI_ECS; XUSB_DEV_XHCI(XUSB_DEV_XHCI_EREPHI) = 0; // Set Event Ring Dequeue Pointer. XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERDPLO) = (XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERDPLO) & 0xF) | evt_ring_addr; XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERDPHI) = 0; } static void _xusb_ep_set_type_and_metrics(u32 ep_idx, volatile xusb_ep_ctx_t *ep_ctxt) { usb_ep_descr_t *ep_desc = NULL; usb_ep_descr_t *endpoints = usbd_xotg->desc->cfg->endpoint; switch (ep_idx) { case XUSB_EP_CTRL_IN: // Set EP type. ep_ctxt->ep_type = EP_TYPE_CNTRL; // Set max packet size based on port speed. ep_ctxt->avg_trb_len = 8; ep_ctxt->max_packet_size = 64; //! TODO USB3: max_packet_size = 512. break; case USB_EP_BULK_OUT: // Set default EP type. ep_ctxt->ep_type = EP_TYPE_BULK_OUT; // Check configuration descriptor. if (usbd_xotg->desc->cfg->interface.bInterfaceClass == 0x3) // HID Class. endpoints = (usb_ep_descr_t *)((void *)endpoints + sizeof(usb_hid_descr_t)); for (u32 i = 0; i < usbd_xotg->desc->cfg->interface.bNumEndpoints; i++) if (endpoints[i].bEndpointAddress == USB_EP_ADDR_BULK_OUT) { ep_desc = &endpoints[i]; break; } // Set actual EP type. if (ep_desc) { switch (ep_desc->bmAttributes) { case USB_EP_TYPE_ISO: ep_ctxt->ep_type = EP_TYPE_ISOC_OUT; break; case USB_EP_TYPE_BULK: ep_ctxt->ep_type = EP_TYPE_BULK_OUT; break; case USB_EP_TYPE_INTR: ep_ctxt->ep_type = EP_TYPE_INTR_OUT; break; } } // Set average TRB length. //TODO: Use ep type instead (we don't expect to calculate avg per gadget)? switch (usbd_xotg->gadget) { case USB_GADGET_UMS: ep_ctxt->avg_trb_len = 3072; break; case USB_GADGET_HID_GAMEPAD: case USB_GADGET_HID_TOUCHPAD: ep_ctxt->avg_trb_len = 1024; break; default: switch (usbd_xotg->port_speed) { case XUSB_SUPER_SPEED: ep_ctxt->avg_trb_len = 1024; break; case XUSB_HIGH_SPEED: case XUSB_FULL_SPEED: ep_ctxt->avg_trb_len = 512; break; } break; } // Set max burst rate. ep_ctxt->max_burst_size = (ep_desc->wMaxPacketSize >> 11) & 3; // Set max packet size based on port speed. if (usbd_xotg->port_speed == XUSB_SUPER_SPEED) { ep_ctxt->max_packet_size = 1024; //! TODO USB3: // If ISO or INTR EP, set Max Esit Payload size. // ep_ctxt->max_burst_size = bMaxBurst; //if (ep_ctxt->ep_type == EP_TYPE_INTR_OUT || ep_ctxt->ep_type == EP_TYPE_ISOC_OUT) // ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size * (ep_ctxt->max_burst_size + 1); } else if (usbd_xotg->port_speed == XUSB_HIGH_SPEED) { ep_ctxt->max_packet_size = 512; // If ISO or INTR EP, set Max Esit Payload size. if (ep_ctxt->ep_type == EP_TYPE_INTR_OUT || ep_ctxt->ep_type == EP_TYPE_ISOC_OUT) ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size * (ep_ctxt->max_burst_size + 1); } else { ep_ctxt->max_packet_size = 64; // If ISO or INTR EP, set Max Esit Payload size. if (ep_ctxt->ep_type == EP_TYPE_INTR_OUT || ep_ctxt->ep_type == EP_TYPE_ISOC_OUT) ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size; } break; case USB_EP_BULK_IN: // Set default EP type. ep_ctxt->ep_type = EP_TYPE_BULK_IN; // Check configuration descriptor. if (usbd_xotg->desc->cfg->interface.bInterfaceClass == 0x3) // HID Class. endpoints = (usb_ep_descr_t *)((void *)endpoints + sizeof(usb_hid_descr_t)); for (u32 i = 0; i < usbd_xotg->desc->cfg->interface.bNumEndpoints; i++) if (endpoints[i].bEndpointAddress == USB_EP_ADDR_BULK_IN) { ep_desc = &endpoints[i]; break; } // Set actual EP type. if (ep_desc) { switch (ep_desc->bmAttributes) { case USB_EP_TYPE_ISO: ep_ctxt->ep_type = EP_TYPE_ISOC_IN; break; case USB_EP_TYPE_BULK: ep_ctxt->ep_type = EP_TYPE_BULK_IN; break; case USB_EP_TYPE_INTR: ep_ctxt->ep_type = EP_TYPE_INTR_IN; break; } } // Set average TRB length. //TODO: Use ep type instead (we don't expect to calculate avg per gadget)? switch (usbd_xotg->gadget) { case USB_GADGET_UMS: ep_ctxt->avg_trb_len = 3072; break; case USB_GADGET_HID_GAMEPAD: case USB_GADGET_HID_TOUCHPAD: ep_ctxt->avg_trb_len = 16; // Normal interrupt avg is 1024KB. break; default: switch (usbd_xotg->port_speed) { case XUSB_SUPER_SPEED: ep_ctxt->avg_trb_len = 1024; break; case XUSB_HIGH_SPEED: case XUSB_FULL_SPEED: ep_ctxt->avg_trb_len = 512; break; } break; } // Set max burst rate. ep_ctxt->max_burst_size = (ep_desc->wMaxPacketSize >> 11) & 3; // Set max packet size based on port speed. if (usbd_xotg->port_speed == XUSB_SUPER_SPEED) { ep_ctxt->max_packet_size = 1024; //! TODO USB3: // If ISO or INTR EP, set Max Esit Payload size. // ep_ctxt->max_burst_size = bMaxBurst; //if (ep_ctxt->ep_type == EP_TYPE_INTR_IN || ep_ctxt->ep_type == EP_TYPE_ISOC_IN) // ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size * (ep_ctxt->max_burst_size + 1); } else if (usbd_xotg->port_speed == XUSB_HIGH_SPEED) { ep_ctxt->max_packet_size = 512; // If ISO or INTR EP, set Max Esit Payload size. if (ep_ctxt->ep_type == EP_TYPE_INTR_IN || ep_ctxt->ep_type == EP_TYPE_ISOC_IN) ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size * (ep_ctxt->max_burst_size + 1); } else { ep_ctxt->max_packet_size = 64; // If ISO or INTR EP, set Max Esit Payload size. if (ep_ctxt->ep_type == EP_TYPE_INTR_IN || ep_ctxt->ep_type == EP_TYPE_ISOC_IN) ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size; } break; } } static int _xusb_ep_init_context(u32 ep_idx) { link_trb_t *link_trb; if (ep_idx > USB_EP_BULK_IN) return USB_ERROR_INIT; if (ep_idx == XUSB_EP_CTRL_OUT) ep_idx = XUSB_EP_CTRL_IN; volatile xusb_ep_ctx_t *ep_ctxt = &xusb_evtq->xusb_ep_ctxt[ep_idx]; memset((void *)ep_ctxt, 0, sizeof(xusb_ep_ctx_t)); ep_ctxt->ep_state = EP_RUNNING; ep_ctxt->dcs = 1; ep_ctxt->cec = 3; ep_ctxt->cerr = 3; ep_ctxt->max_burst_size = 0; switch (ep_idx) { case XUSB_EP_CTRL_IN: usbd_xotg->cntrl_producer_cycle = 1; usbd_xotg->cntrl_epenqueue_ptr = xusb_evtq->xusb_cntrl_event_queue; usbd_xotg->cntrl_epdequeue_ptr = xusb_evtq->xusb_cntrl_event_queue; _xusb_ep_set_type_and_metrics(ep_idx, ep_ctxt); ep_ctxt->trd_dequeueptr_lo = (u32)xusb_evtq->xusb_cntrl_event_queue >> 4; ep_ctxt->trd_dequeueptr_hi = 0; link_trb = (link_trb_t *)&xusb_evtq->xusb_cntrl_event_queue[XUSB_LINK_TRB_IDX]; link_trb->toggle_cycle = 1; link_trb->ring_seg_ptrlo = (u32)xusb_evtq->xusb_cntrl_event_queue >> 4; link_trb->ring_seg_ptrhi = 0; link_trb->trb_type = XUSB_TRB_LINK; break; case USB_EP_BULK_OUT: usbd_xotg->bulkout_producer_cycle = 1; usbd_xotg->bulkout_epenqueue_ptr = xusb_evtq->xusb_bulkout_event_queue; usbd_xotg->bulkout_epdequeue_ptr = xusb_evtq->xusb_bulkout_event_queue; _xusb_ep_set_type_and_metrics(ep_idx, ep_ctxt); ep_ctxt->trd_dequeueptr_lo = (u32)xusb_evtq->xusb_bulkout_event_queue >> 4; ep_ctxt->trd_dequeueptr_hi = 0; link_trb = (link_trb_t *)&xusb_evtq->xusb_bulkout_event_queue[XUSB_LINK_TRB_IDX]; link_trb->toggle_cycle = 1; link_trb->ring_seg_ptrlo = (u32)xusb_evtq->xusb_bulkout_event_queue >> 4; link_trb->ring_seg_ptrhi = 0; link_trb->trb_type = XUSB_TRB_LINK; break; case USB_EP_BULK_IN: usbd_xotg->bulkin_producer_cycle = 1; usbd_xotg->bulkin_epenqueue_ptr = xusb_evtq->xusb_bulkin_event_queue; usbd_xotg->bulkin_epdequeue_ptr = xusb_evtq->xusb_bulkin_event_queue; _xusb_ep_set_type_and_metrics(ep_idx, ep_ctxt); ep_ctxt->trd_dequeueptr_lo = (u32)xusb_evtq->xusb_bulkin_event_queue >> 4; ep_ctxt->trd_dequeueptr_hi = 0; link_trb = (link_trb_t *)&xusb_evtq->xusb_bulkin_event_queue[XUSB_LINK_TRB_IDX]; link_trb->toggle_cycle = 1; link_trb->ring_seg_ptrlo = (u32)xusb_evtq->xusb_bulkin_event_queue >> 4; link_trb->ring_seg_ptrhi = 0; link_trb->trb_type = XUSB_TRB_LINK; break; } return USB_RES_OK; } static int _xusbd_ep_initialize(u32 ep_idx) { switch (ep_idx) { case XUSB_EP_CTRL_IN: case XUSB_EP_CTRL_OUT: return _xusb_ep_init_context(XUSB_EP_CTRL_IN); case USB_EP_BULK_OUT: case USB_EP_BULK_IN: _xusb_ep_init_context(ep_idx); XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_RELOAD) = BIT(ep_idx); int res = _xusb_xhci_mask_wait(XUSB_DEV_XHCI_EP_RELOAD, BIT(ep_idx), 0, 1000); if (!res) { XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_PAUSE) &= ~BIT(ep_idx); XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_HALT) &= ~BIT(ep_idx); } return res; default: return USB_ERROR_INIT; } } static void _xusbd_ep1_disable(u32 ep_idx) { volatile xusb_ep_ctx_t *ep_ctxt = &xusb_evtq->xusb_ep_ctxt[ep_idx]; u32 ep_mask = BIT(ep_idx); switch (ep_idx) { case USB_EP_BULK_OUT: case USB_EP_BULK_IN: // Skip if already disabled. if (!ep_ctxt->ep_state) return; XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_HALT) |= ep_mask; // Set EP state to disabled. ep_ctxt->ep_state = EP_DISABLED; // Wait for EP status to change. _xusb_xhci_mask_wait(XUSB_DEV_XHCI_EP_STCHG, ep_mask, ep_mask, 1000); // Clear status change. XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_STCHG) = ep_mask; break; } } static void _xusb_disable_ep1() { _xusbd_ep1_disable(USB_EP_BULK_OUT); _xusbd_ep1_disable(USB_EP_BULK_IN); // Device mode stop. XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) &= ~XHCI_CTRL_RUN; XUSB_DEV_XHCI(XUSB_DEV_XHCI_ST) |= XHCI_ST_RC; usbd_xotg->config_num = 0; usbd_xotg->interface_num = 0; usbd_xotg->max_lun_set = false; usbd_xotg->device_state = XUSB_DEFAULT; } static void _xusb_init_phy() { // Configure and enable PLLU. clock_enable_pllu(); // Enable IDDQ control by software and disable UTMIPLL IDDQ. CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) = (CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) & 0xFFFFFFFC) | 1; // Set UTMIPLL dividers and config based on OSC and enable it to 960 MHz. clock_enable_utmipll(); // Set UTMIP misc config. CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) = (CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) & 0xFEFFFFE8) | 0x2000008 | 0x20 | 2; usleep(2); // Set OTG PAD0 calibration. u32 fuse_usb_calib = FUSE(FUSE_USB_CALIB); // Set HS_CURR_LEVEL. XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_0) = (XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_0) & 0xFFFFFFC0) | (fuse_usb_calib & 0x3F); // Set TERM_RANGE_ADJ and RPD_CTRL. XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_1) = (XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_1) & 0x83FFFF87) | ((fuse_usb_calib & 0x780) >> 4) | ((u32)(FUSE(FUSE_USB_CALIB_EXT) << 27) >> 1); // Set VREG_LEV to 1. XUSB_PADCTL(XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD0_CTL1) = (XUSB_PADCTL(XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD0_CTL1) & 0xFFFFFE3F) | 0x80; // Disable power down on usb2 ports pads. XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_0) &= 0xDBFFFFFF; // Clear pad power down. XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_1) &= 0xFFFFFFFB; // Clear pad dr power down. XUSB_PADCTL(XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD0_CTL0) &= 0xFFFFFFFE; // Clear charging power down. XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_0) &= 0xFFFFF7FF; // Clear bias power down. (void)XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_1); // Commit write. // Enable USB2 tracking clock. CLOCK(CLK_RST_CONTROLLER_CLK_ENB_Y_SET) = BIT(CLK_Y_USB2_TRK); CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_USB2_HSIC_TRK) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_USB2_HSIC_TRK) & 0xFFFFFF00) | 6; // Set trank divisor to 4. // Set tracking parameters and trigger it. XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_1) = 0x451E000; XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_1) = 0x51E000; usleep(100); // TRK cycle done. Force PDTRK input into power down. XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_1) = 0x451E000; usleep(3); // Re-trigger it. XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_1) = 0x51E000; usleep(100); // TRK cycle done. Force PDTRK input into power down. XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_1) |= 0x4000000; // Disable USB2 tracking clock. CLOCK(CLK_RST_CONTROLLER_CLK_ENB_Y_CLR) = BIT(CLK_Y_USB2_TRK); // Wait for XUSB PHY to stabilize. usleep(30); } static void _xusbd_init_device_clocks() { // Disable reset to PLLU_OUT1 CLOCK(CLK_RST_CONTROLLER_PLLU_OUTA) |= 1; usleep(2); // Enable XUSB device clock. CLOCK(CLK_RST_CONTROLLER_CLK_ENB_U_SET) = BIT(CLK_U_XUSB_DEV); // Set XUSB device core clock source to PLLP for a 102MHz result. CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_CORE_DEV) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_CORE_DEV) & 0x1FFFFF00) | (1 << 29) | 6; usleep(2); // Set XUSB Full-Speed logic clock source to FO 48MHz. CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_FS) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_FS) & 0x1FFFFFFF) | (2 << 29); // Enable XUSB Super-Speed logic clock. CLOCK(CLK_RST_CONTROLLER_CLK_ENB_W_SET) = BIT(CLK_W_XUSB_SS); // Set XUSB Super-Speed logic clock source to HSIC 480MHz for 120MHz result and source FS logic clock from Super-Speed. CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_SS) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_SS) & 0x1FFFFF00) | (3 << 29) | 6; // Clear reset to XUSB device and Super-Speed logic. CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_CLR) = BIT(CLK_W_XUSB_SS); CLOCK(CLK_RST_CONTROLLER_RST_DEV_U_CLR) = BIT(CLK_U_XUSB_DEV); usleep(2); } int xusb_device_init() { // Disable USB2 device controller clocks. CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_SET) = BIT(CLK_L_USBD); CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_CLR) = BIT(CLK_L_USBD); // Enable XUSB clock and clear Reset to XUSB Pad Control. CLOCK(CLK_RST_CONTROLLER_CLK_ENB_W_SET) = BIT(CLK_W_XUSB); CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_SET) = BIT(CLK_W_XUSB); usleep(2); CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_CLR) = BIT(CLK_W_XUSB); CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_CLR) = BIT(CLK_W_XUSB_PADCTL); usleep(2); // USB2 Pads to XUSB. XUSB_PADCTL(XUSB_PADCTL_USB2_PAD_MUX) = (XUSB_PADCTL(XUSB_PADCTL_USB2_PAD_MUX) & ~(PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK | PADCTL_USB2_PAD_MUX_USB2_OTG_PAD_PORT0_MASK)) | PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB | PADCTL_USB2_PAD_MUX_USB2_OTG_PAD_PORT0_XUSB; // Initialize XUSB controller PHY. _xusb_init_phy(); // Set USB2.0 Port 0 to device mode. XUSB_PADCTL(XUSB_PADCTL_USB2_PORT_CAP) = (XUSB_PADCTL(XUSB_PADCTL_USB2_PORT_CAP) & ~PADCTL_USB2_PORT_CAP_PORT_0_CAP_MASK) | PADCTL_USB2_PORT_CAP_PORT_0_CAP_DEV; //! TODO USB3 // // Set USB3.0 Port 0 cap to device. // XUSB_PADCTL(XUSB_PADCTL_SS_PORT_CAP) = (XUSB_PADCTL(XUSB_PADCTL_SS_PORT_CAP) & ~PADCTL_SS_PORT_CAP_0_PORT1_CAP_MASK) | PADCTL_SS_PORT_CAP_0_PORT1_CAP_DEVICE_ONLY; // Set Super Speed Port 0 to USB2 Port 0. XUSB_PADCTL(XUSB_PADCTL_SS_PORT_MAP) &= ~PADCTL_SS_PORT_MAP_PORT0_MASK; // 0: USB2_PORT0 // Power Up ID Wake up and Vbus Wake Up for UTMIP PMC(APBDEV_PMC_USB_AO) &= 0xFFFFFFF3; usleep(1); // Initialize device clocks. _xusbd_init_device_clocks(); // Enable AHB redirect for access to IRAM for Event/EP ring buffers. mc_enable_ahb_redirect(); // Enable XUSB device IPFS. XUSB_DEV_DEV(XUSB_DEV_CONFIGURATION) |= DEV_CONFIGURATION_EN_FPCI; // Configure PCI and BAR0 address space. XUSB_DEV_PCI(XUSB_CFG_1) |= CFG_1_BUS_MASTER | CFG_1_MEMORY_SPACE | CFG_1_IO_SPACE; usleep(1); XUSB_DEV_PCI(XUSB_CFG_4) = XUSB_DEV_BASE | CFG_4_ADDRESS_TYPE_32_BIT; // Mask SATA interrupt to MCORE. XUSB_DEV_DEV(XUSB_DEV_INTR_MASK) |= DEV_INTR_MASK_IP_INT_MASK; // AHB USB performance cfg. AHB_GIZMO(AHB_GIZMO_AHB_MEM) |= AHB_MEM_DONT_SPLIT_AHB_WR | AHB_MEM_ENB_FAST_REARBITRATE; AHB_GIZMO(AHB_GIZMO_USB3) |= AHB_GIZMO_IMMEDIATE; AHB_GIZMO(AHB_ARBITRATION_PRIORITY_CTRL) = PRIORITY_CTRL_WEIGHT(7) | PRIORITY_SELECT_USB3; AHB_GIZMO(AHB_AHB_MEM_PREFETCH_CFG1) = MEM_PREFETCH_ENABLE | MEM_PREFETCH_USB3_MST_ID | MEM_PREFETCH_ADDR_BNDRY(12) | 0x1000; // Addr boundary 64KB, Inactivity 4096 cycles. // Initialize context. usbd_xotg = &usbd_xotg_controller_ctxt; memset(usbd_xotg, 0, sizeof(xusbd_controller_t)); // Initialize event and EP rings. _xusbd_ep_init_event_ring(); memset(xusb_evtq->xusb_cntrl_event_queue, 0, sizeof(xusb_evtq->xusb_cntrl_event_queue)); memset(xusb_evtq->xusb_bulkin_event_queue, 0, sizeof(xusb_evtq->xusb_bulkin_event_queue)); memset(xusb_evtq->xusb_bulkout_event_queue, 0, sizeof(xusb_evtq->xusb_bulkout_event_queue)); // Initialize Control EP. int res = _xusbd_ep_initialize(XUSB_EP_CTRL_IN); if (res) return USB_ERROR_INIT; // Enable events and interrupts. XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) |= XHCI_CTRL_IE | XHCI_CTRL_LSE; XUSB_DEV_XHCI(XUSB_DEV_XHCI_ECPLO) = (u32)xusb_evtq->xusb_ep_ctxt & 0xFFFFFFF0; XUSB_DEV_XHCI(XUSB_DEV_XHCI_ECPHI) = 0; //! TODO USB3: // XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT) |= DEV_XHCI_PORTHALT_STCHG_INTR_EN; return USB_RES_OK; } //! TODO: Power down more stuff. static void _xusb_device_power_down() { // Disable clock for XUSB Super-Speed and set source to CLK_M. CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_SET) = BIT(CLK_W_XUSB_SS); CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_SS) &= 0x1FFFFF00; usleep(2); CLOCK(CLK_RST_CONTROLLER_CLK_ENB_W_CLR) = BIT(CLK_W_XUSB_SS); // Put XUSB device into reset. CLOCK(CLK_RST_CONTROLLER_RST_DEV_U_SET) = BIT(CLK_U_XUSB_DEV); usleep(2); // Reset Full-Speed clock source to CLK_M and div1. CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_FS) = 0; usleep(2); // Disable XUSB device clock. CLOCK(CLK_RST_CONTROLLER_CLK_ENB_U_CLR) = BIT(CLK_U_XUSB_DEV); // Force UTMIP_PLL power down. CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG1) &= (~BIT(15)); CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) |= BIT(4) | BIT(0); // UTMIP_FORCE_PD_SAMP_A/C_POWERDOWN. // Force enable UTMIPLL IDDQ. CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) |= 3; // Disable PLLU. clock_disable_pllu(); // Set XUSB_PADCTL clock reset. CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_SET) = BIT(CLK_W_XUSB_PADCTL); // Disable XUSB clock. CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_SET) = BIT(CLK_W_XUSB); CLOCK(CLK_RST_CONTROLLER_CLK_ENB_W_CLR) = BIT(CLK_W_XUSB); } static int _xusb_queue_trb(u32 ep_idx, void *trb, bool ring_doorbell) { int res = USB_RES_OK; data_trb_t *next_trb; link_trb_t *link_trb; // Copy TRB and advance Enqueue list. switch (ep_idx) { case XUSB_EP_CTRL_IN: memcpy(usbd_xotg->cntrl_epenqueue_ptr, trb, sizeof(data_trb_t)); // Advance queue and if Link TRB set index to 0 and toggle cycle bit. next_trb = &usbd_xotg->cntrl_epenqueue_ptr[1]; if (next_trb->trb_type == XUSB_TRB_LINK) { link_trb = (link_trb_t *)next_trb; link_trb->cycle = usbd_xotg->cntrl_producer_cycle & 1; link_trb->toggle_cycle = 1; next_trb = (data_trb_t *)(link_trb->ring_seg_ptrlo << 4); usbd_xotg->cntrl_producer_cycle ^= 1; } usbd_xotg->cntrl_epenqueue_ptr = next_trb; break; case USB_EP_BULK_OUT: memcpy(usbd_xotg->bulkout_epenqueue_ptr, trb, sizeof(data_trb_t)); // Advance queue and if Link TRB set index to 0 and toggle cycle bit. next_trb = &usbd_xotg->bulkout_epenqueue_ptr[1]; if (next_trb->trb_type == XUSB_TRB_LINK) { link_trb = (link_trb_t *)next_trb; link_trb->cycle = usbd_xotg->bulkout_producer_cycle & 1; link_trb->toggle_cycle = 1; next_trb = (data_trb_t *)(link_trb->ring_seg_ptrlo << 4); usbd_xotg->bulkout_producer_cycle ^= 1; } usbd_xotg->bulkout_epenqueue_ptr = next_trb; break; case USB_EP_BULK_IN: memcpy(usbd_xotg->bulkin_epenqueue_ptr, trb, sizeof(data_trb_t)); // Advance queue and if Link TRB set index to 0 and toggle cycle bit. next_trb = &usbd_xotg->bulkin_epenqueue_ptr[1]; if (next_trb->trb_type == XUSB_TRB_LINK) { link_trb = (link_trb_t *)next_trb; link_trb->cycle = usbd_xotg->bulkin_producer_cycle & 1; link_trb->toggle_cycle = 1; next_trb = (data_trb_t *)(link_trb->ring_seg_ptrlo << 4); usbd_xotg->bulkin_producer_cycle ^= 1; } usbd_xotg->bulkin_epenqueue_ptr = next_trb; break; case XUSB_EP_CTRL_OUT: default: res = XUSB_ERROR_INVALID_EP; break; } // Ring doorbell. if (ring_doorbell) { // Flush data before transfer. bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLEAN_WAY, false); u32 target_id = (ep_idx << 8) & 0xFFFF; if (ep_idx == XUSB_EP_CTRL_IN) target_id |= usbd_xotg->ctrl_seq_num << 16; XUSB_DEV_XHCI(XUSB_DEV_XHCI_DB) = target_id; } return res; } static void _xusb_create_status_trb(status_trb_t *trb, usb_dir_t direction) { trb->cycle = usbd_xotg->cntrl_producer_cycle & 1; trb->ioc = 1; // Enable interrupt on completion. trb->trb_type = XUSB_TRB_STATUS; trb->dir = direction; } static void _xusb_create_normal_trb(normal_trb_t *trb, u8 *buf, u32 len, usb_dir_t direction) { u8 producer_cycle; trb->databufptr_lo = (u32)buf; trb->databufptr_hi = 0; trb->trb_tx_len = len; // Single TRB transfer. trb->td_size = 0; trb->chain = 0; if (direction == USB_DIR_IN) producer_cycle = usbd_xotg->bulkin_producer_cycle & 1; else producer_cycle = usbd_xotg->bulkout_producer_cycle & 1; trb->cycle = producer_cycle; trb->isp = 1; // Enable interrupt on short packet. trb->ioc = 1; // Enable interrupt on completion. trb->trb_type = XUSB_TRB_NORMAL; } static void _xusb_create_data_trb(data_trb_t *trb, u8 *buf, u32 len, usb_dir_t direction) { trb->databufptr_lo = (u32)buf; trb->databufptr_hi = 0; trb->trb_tx_len = len; // Single TRB transfer. trb->td_size = 0; trb->chain = 0; trb->cycle = usbd_xotg->cntrl_producer_cycle & 1; trb->isp = 1; // Enable interrupt on short packet. trb->ioc = 1; // Enable interrupt on completion. trb->trb_type = XUSB_TRB_DATA; trb->dir = direction; } static int _xusb_issue_status_trb(usb_dir_t direction) { int res = USB_RES_OK; status_trb_t trb = {0}; if (usbd_xotg->cntrl_epenqueue_ptr == usbd_xotg->cntrl_epdequeue_ptr || direction == USB_DIR_OUT) { _xusb_create_status_trb(&trb, direction); res = _xusb_queue_trb(XUSB_EP_CTRL_IN, &trb, EP_RING_DOORBELL); usbd_xotg->wait_for_event_trb = XUSB_TRB_STATUS; } return res; } static int _xusb_issue_normal_trb(u8 *buf, u32 len, usb_dir_t direction) { normal_trb_t trb = {0}; _xusb_create_normal_trb(&trb, buf, len, direction); u32 ep_idx = USB_EP_BULK_IN; if (direction == USB_DIR_OUT) ep_idx = USB_EP_BULK_OUT; int res = _xusb_queue_trb(ep_idx, &trb, EP_RING_DOORBELL); if (!res) usbd_xotg->wait_for_event_trb = XUSB_TRB_NORMAL; return res; } static int _xusb_issue_data_trb(u8 *buf, u32 len, usb_dir_t direction) { data_trb_t trb = {0}; int res = USB_RES_OK; if (usbd_xotg->cntrl_epenqueue_ptr == usbd_xotg->cntrl_epdequeue_ptr) { _xusb_create_data_trb(&trb, buf, len, direction); res = _xusb_queue_trb(XUSB_EP_CTRL_IN, &trb, EP_RING_DOORBELL); if (!res) usbd_xotg->wait_for_event_trb = XUSB_TRB_DATA; } return res; } int xusb_set_ep_stall(u32 endpoint, int ep_stall) { u32 ep_mask = BIT(endpoint); if (ep_stall) XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_HALT) |= ep_mask; else XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_HALT) &= ~ep_mask; // Wait for EP status to change. int res = _xusb_xhci_mask_wait(XUSB_DEV_XHCI_EP_STCHG, ep_mask, ep_mask, 1000); if (res) return res; // Clear status change. XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_STCHG) = ep_mask; return USB_RES_OK; } static int _xusb_wait_ep_stopped(u32 endpoint) { u32 ep_mask = BIT(endpoint); // Wait for EP status to change. _xusb_xhci_mask_wait(XUSB_DEV_XHCI_EP_STOPPED, ep_mask, ep_mask, 1000); // Clear status change. XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_STOPPED) = ep_mask; return USB_RES_OK; } static int _xusb_handle_transfer_event(transfer_event_trb_t *trb) { // Advance dequeue list. data_trb_t *next_trb; switch (trb->ep_id) { case XUSB_EP_CTRL_IN: next_trb = &usbd_xotg->cntrl_epdequeue_ptr[1]; if (next_trb->trb_type == XUSB_TRB_LINK) next_trb = (data_trb_t *)(next_trb->databufptr_lo & 0xFFFFFFF0); usbd_xotg->cntrl_epdequeue_ptr = next_trb; break; case USB_EP_BULK_OUT: next_trb = &usbd_xotg->bulkout_epdequeue_ptr[1]; if (next_trb->trb_type == XUSB_TRB_LINK) next_trb = (data_trb_t *)(next_trb->databufptr_lo & 0xFFFFFFF0); usbd_xotg->bulkout_epdequeue_ptr = next_trb; break; case USB_EP_BULK_IN: next_trb = &usbd_xotg->bulkin_epdequeue_ptr[1]; if (next_trb->trb_type == XUSB_TRB_LINK) next_trb = (data_trb_t *)(next_trb->databufptr_lo & 0xFFFFFFF0); usbd_xotg->bulkin_epdequeue_ptr = next_trb; break; default: // Should never happen. break; } // Handle completion code. switch (trb->comp_code) { case XUSB_COMP_SUCCESS: case XUSB_COMP_SHORT_PKT: switch (trb->ep_id) { case XUSB_EP_CTRL_IN: if (usbd_xotg->wait_for_event_trb == XUSB_TRB_DATA) return _xusb_issue_status_trb(USB_DIR_OUT); else if (usbd_xotg->wait_for_event_trb == XUSB_TRB_STATUS) { switch (usbd_xotg->device_state) { case XUSB_ADDRESSED_STS_WAIT: usbd_xotg->device_state = XUSB_ADDRESSED; break; case XUSB_CONFIGURED_STS_WAIT: usbd_xotg->device_state = XUSB_CONFIGURED; break; case XUSB_LUN_CONFIGURED_STS_WAIT: usbd_xotg->device_state = XUSB_LUN_CONFIGURED; break; case XUSB_HID_CONFIGURED_STS_WAIT: usbd_xotg->device_state = XUSB_HID_CONFIGURED; break; } } break; case USB_EP_BULK_IN: usbd_xotg->tx_bytes[USB_DIR_IN] -= trb->trb_tx_len; if (usbd_xotg->tx_count[USB_DIR_IN]) usbd_xotg->tx_count[USB_DIR_IN]--; // If bytes remaining for a Bulk IN transfer, return error. if (trb->trb_tx_len) return XUSB_ERROR_XFER_BULK_IN_RESIDUE; break; case USB_EP_BULK_OUT: // If short packet and Bulk OUT, it's not an error because we prime EP for 4KB. usbd_xotg->tx_bytes[USB_DIR_OUT] -= trb->trb_tx_len; if (usbd_xotg->tx_count[USB_DIR_OUT]) usbd_xotg->tx_count[USB_DIR_OUT]--; break; } return USB_RES_OK; /* case XUSB_COMP_USB_TRANSACTION_ERROR: case XUSB_COMP_TRB_ERROR: case XUSB_COMP_RING_UNDERRUN: case XUSB_COMP_RING_OVERRUN: case XUSB_COMP_CTRL_DIR_ERROR: // Redefined. xusb_set_ep_stall(trb->ep_id, USB_EP_CFG_STALL); return USB_RES_OK; */ case XUSB_COMP_BABBLE_DETECTED_ERROR: _xusb_wait_ep_stopped(trb->ep_id); xusb_set_ep_stall(trb->ep_id, USB_EP_CFG_STALL); return XUSB_ERROR_BABBLE_DETECTED; case XUSB_COMP_CTRL_DIR_ERROR: return XUSB_ERROR_XFER_DIR; case XUSB_COMP_CTRL_SEQ_NUM_ERROR: return XUSB_ERROR_SEQ_NUM; //! TODO: Can mean a new setup packet was received. default: // Every other completion code. return USB_ERROR_XFER_ERROR; } } /* * Other XUSB impl: * CBT: PR, PRC, WPR, WRC, CSC, REQ, PLC, CEC. * LNX: REQ, PRC PR, PRC & !PR, WRC, CSC, PLC, CEC. * BRO: CSC, PR | PRC, WPR | WRC, REQ, PLC, CEC. */ static int _xusb_handle_port_change() { u32 status = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC); u32 halt = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT); u32 clear_mask = XHCI_PORTSC_CEC | XHCI_PORTSC_PLC | XHCI_PORTSC_PRC | XHCI_PORTSC_WRC | XHCI_PORTSC_CSC; // Port reset (PR). if (status & XHCI_PORTSC_PR) { //! TODO: // XHCI_PORTSC_PR: device_state = XUSB_RESET //_disable_usb_wdt4(); } // Port Reset Change (PRC). if (status & XHCI_PORTSC_PRC) { // Clear PRC bit. status &= ~clear_mask; status |= XHCI_PORTSC_PRC; XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) = status; } // Warm Port Reset (WPR). if (status & XHCI_PORTSC_WPR) { //_disable_usb_wdt4(); XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT) &= ~XHCI_PORTHALT_HALT_LTSSM; (void)XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT); //! TODO: XHCI_PORTSC_WPR: device_state = XUSB_RESET } // Warm Port Reset Change (WRC). if (status & XHCI_PORTSC_WRC) { // Clear WRC bit. status &= ~clear_mask; status |= XHCI_PORTSC_WRC; XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) = status; } // Reread port status to handle more changes. status = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC); // Connect Status Change (CSC). if (status & XHCI_PORTSC_CSC) { //! TODO: Check CCS. // CCS check seems to be // XHCI_PORTSC_CCS 1: device_state = XUSB_CONNECTED // XHCI_PORTSC_CCS 0: device_state = XUSB_DISCONNECTED // Always do XHCI_PORTSC_CSC bit clear. // Set port speed. usbd_xotg->port_speed = (status & XHCI_PORTSC_PS) >> 10; // In case host does not support Super Speed, revert the control EP packet size. if (usbd_xotg->port_speed != XUSB_SUPER_SPEED) { volatile xusb_ep_ctx_t *ep_ctxt = &xusb_evtq->xusb_ep_ctxt[XUSB_EP_CTRL_IN]; ep_ctxt->avg_trb_len = 8; ep_ctxt->max_packet_size = 64; //! TODO: If super speed is supported, ep context reload, unpause and unhalt must happen. } // Clear CSC bit. status &= ~clear_mask; status |= XHCI_PORTSC_CSC; XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) = status; } // Handle Config Request (STCHG_REQ). if (halt & XHCI_PORTHALT_STCHG_REQ) { // Clear Link Training Status and pending request/reject. XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT) &= ~XHCI_PORTHALT_HALT_LTSSM; (void)XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT); } // Reread port status to handle more changes. status = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC); // Port link state change (PLC). if (status & XHCI_PORTSC_PLC) { // check XHCI_PORTSC_PLS_MASK // if XHCI_PORTSC_PLS_U3 // device_state = XUSB_SUSPENDED // else if XHCI_PORTSC_PLS_U0 and XUSB_SUSPENDED // val = XUSB_DEV_XHCI_EP_PAUSE // XUSB_DEV_XHCI_EP_PAUSE = 0 // XUSB_DEV_XHCI_EP_STCHG = val; // Clear PLC bit. status &= ~clear_mask; status |= XHCI_PORTSC_PLC; XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) = status; } // Port configuration link error (CEC). if (status & XHCI_PORTSC_CEC) { status = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC); status &= ~clear_mask; status |= XHCI_PORTSC_CEC; XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) = status; return XUSB_ERROR_PORT_CFG; } return USB_RES_OK; } static int _xusb_handle_get_ep_status(u32 ep_idx) { u32 ep_mask = BIT(ep_idx); static u8 xusb_ep_status_descriptor[2] = {0}; xusb_ep_status_descriptor[0] = (XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_HALT) & ep_mask) ? USB_STATUS_EP_HALTED : USB_STATUS_EP_OK; return _xusb_issue_data_trb(xusb_ep_status_descriptor, 2, USB_DIR_IN); } static int _xusb_handle_get_class_request(usb_ctrl_setup_t *ctrl_setup) { u8 _bRequest = ctrl_setup->bRequest; u16 _wIndex = ctrl_setup->wIndex; u16 _wValue = ctrl_setup->wValue; u16 _wLength = ctrl_setup->wLength; bool valid_interface = _wIndex == usbd_xotg->interface_num; bool valid_len = (_bRequest == USB_REQUEST_BULK_GET_MAX_LUN) ? 1 : 0; if (!valid_interface || _wValue != 0 || _wLength != valid_len) goto stall; switch (_bRequest) { case USB_REQUEST_BULK_RESET: usbd_xotg->bulk_reset_req = true; return _xusb_issue_status_trb(USB_DIR_IN); // DELAYED_STATUS; case USB_REQUEST_BULK_GET_MAX_LUN: if (!usbd_xotg->max_lun_set) goto stall; usbd_xotg->device_state = XUSB_LUN_CONFIGURED_STS_WAIT; return _xusb_issue_data_trb(&usbd_xotg->max_lun, 1, USB_DIR_IN); } stall: xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); return USB_RES_OK; } static int _xusb_handle_get_descriptor(usb_ctrl_setup_t *ctrl_setup) { u32 size; void *descriptor; u32 wLength = ctrl_setup->wLength; u8 descriptor_type = ctrl_setup->wValue >> 8; u8 descriptor_subtype = ctrl_setup->wValue & 0xFF; switch (descriptor_type) { case USB_DESCRIPTOR_DEVICE: //! TODO USB3: Provide a super speed descriptor. /* u32 soc_rev = APB_MISC(APB_MISC_GP_HIDREV); usb_device_descriptor.idProduct = (soc_rev >> 8) & 0xFF; // chip_id. usb_device_descriptor.idProduct |= ((soc_rev << 4) | (FUSE(FUSE_SKU_INFO) & 0xF)) << 8; // HIDFAM. usb_device_descriptor.bcdDevice = (soc_rev >> 16) & 0xF; // MINORREV. usb_device_descriptor.bcdDevice |= ((soc_rev >> 4) & 0xF) << 8; // MAJORREV. */ descriptor = usbd_xotg->desc->dev; size = usbd_xotg->desc->dev->bLength; break; case USB_DESCRIPTOR_CONFIGURATION: //! TODO USB3: Provide a super speed descriptor. if (usbd_xotg->gadget == USB_GADGET_UMS) { if (usbd_xotg->port_speed == XUSB_HIGH_SPEED) // High speed. 512 bytes. { usbd_xotg->desc->cfg->endpoint[0].wMaxPacketSize = 0x200; // No burst. usbd_xotg->desc->cfg->endpoint[1].wMaxPacketSize = 0x200; // No burst. } else // Full speed. 64 bytes. { usbd_xotg->desc->cfg->endpoint[0].wMaxPacketSize = 0x40; usbd_xotg->desc->cfg->endpoint[1].wMaxPacketSize = 0x40; } } else { usb_cfg_hid_descr_t *tmp = (usb_cfg_hid_descr_t *)usbd_xotg->desc->cfg; if (usbd_xotg->port_speed == XUSB_HIGH_SPEED) // High speed. 512 bytes. { tmp->endpoint[0].wMaxPacketSize = 0x200; tmp->endpoint[1].wMaxPacketSize = 0x200; tmp->endpoint[0].bInterval = usbd_xotg->gadget == USB_GADGET_HID_GAMEPAD ? 4 : 3; // 8ms : 4ms. tmp->endpoint[1].bInterval = usbd_xotg->gadget == USB_GADGET_HID_GAMEPAD ? 4 : 3; // 8ms : 4ms. } else // Full speed. 64 bytes. { tmp->endpoint[0].wMaxPacketSize = 0x40; tmp->endpoint[1].wMaxPacketSize = 0x40; tmp->endpoint[0].bInterval = usbd_xotg->gadget == USB_GADGET_HID_GAMEPAD ? 8 : 4; // 8ms : 4ms. tmp->endpoint[1].bInterval = usbd_xotg->gadget == USB_GADGET_HID_GAMEPAD ? 8 : 4; // 8ms : 4ms. } } descriptor = usbd_xotg->desc->cfg; size = usbd_xotg->desc->cfg->config.wTotalLength; break; case USB_DESCRIPTOR_STRING: switch (descriptor_subtype) { case 1: descriptor = usbd_xotg->desc->vendor; size = usbd_xotg->desc->vendor[0]; break; case 2: descriptor = usbd_xotg->desc->product; size = usbd_xotg->desc->product[0]; break; case 3: descriptor = usbd_xotg->desc->serial; size = usbd_xotg->desc->serial[0]; break; case 0xEE: descriptor = usbd_xotg->desc->ms_os; size = usbd_xotg->desc->ms_os->bLength; break; default: descriptor = usbd_xotg->desc->lang_id; size = 4; break; } break; case USB_DESCRIPTOR_DEVICE_QUALIFIER: if (!usbd_xotg->desc->dev_qual) { xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); return USB_RES_OK; } usbd_xotg->desc->dev_qual->bNumOtherConfigs = 0; descriptor = usbd_xotg->desc->dev_qual; size = usbd_xotg->desc->dev_qual->bLength; break; case USB_DESCRIPTOR_OTHER_SPEED_CONFIGURATION: if (!usbd_xotg->desc->cfg_other) { xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); return USB_RES_OK; } if (usbd_xotg->port_speed == XUSB_HIGH_SPEED) { usbd_xotg->desc->cfg_other->endpoint[0].wMaxPacketSize = 0x40; usbd_xotg->desc->cfg_other->endpoint[1].wMaxPacketSize = 0x40; } else { usbd_xotg->desc->cfg_other->endpoint[0].wMaxPacketSize = 0x200; usbd_xotg->desc->cfg_other->endpoint[1].wMaxPacketSize = 0x200; } descriptor = usbd_xotg->desc->cfg_other; size = usbd_xotg->desc->cfg_other->config.wTotalLength; break; case USB_DESCRIPTOR_DEVICE_BINARY_OBJECT: descriptor = usbd_xotg->desc->dev_bot; size = usbd_xotg->desc->dev_bot->wTotalLength; break; default: xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); return USB_RES_OK; } if (wLength < size) size = wLength; return _xusb_issue_data_trb(descriptor, size, USB_DIR_IN); } static void _xusb_handle_set_request_dev_address(usb_ctrl_setup_t *ctrl_setup) { u32 addr = ctrl_setup->wValue & 0xFF; XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) = (XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) & 0x80FFFFFF) | (addr << 24); xusb_evtq->xusb_ep_ctxt[XUSB_EP_CTRL_IN].device_addr = addr; _xusb_issue_status_trb(USB_DIR_IN); usbd_xotg->device_state = XUSB_ADDRESSED_STS_WAIT; } static void _xusb_handle_set_request_configuration(usb_ctrl_setup_t *ctrl_setup) { usbd_xotg->config_num = ctrl_setup->wValue; // Remove configuration. if (!usbd_xotg->config_num) { //! TODO: Signal that to userspace. _xusb_disable_ep1(); _xusb_issue_status_trb(USB_DIR_IN); return; } // Initialize BULK EPs. _xusbd_ep_initialize(USB_EP_BULK_OUT); _xusbd_ep_initialize(USB_EP_BULK_IN); // Device mode start. XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) |= XHCI_CTRL_RUN; XUSB_DEV_XHCI(XUSB_DEV_XHCI_ST) |= XHCI_ST_RC; _xusb_issue_status_trb(USB_DIR_IN); usbd_xotg->device_state = XUSB_CONFIGURED_STS_WAIT; } static int _xusbd_handle_ep0_control_transfer(usb_ctrl_setup_t *ctrl_setup) { u32 size; u8 *desc; bool ep_stall = false; bool transmit_data = false; u8 _bmRequestType = ctrl_setup->bmRequestType; u8 _bRequest = ctrl_setup->bRequest; u16 _wValue = ctrl_setup->wValue; u16 _wIndex = ctrl_setup->wIndex; u16 _wLength = ctrl_setup->wLength; static u8 xusb_dev_status_descriptor[2] = {USB_STATUS_DEV_SELF_POWERED, 0}; static u8 xusb_interface_descriptor[4] = {0}; static u8 xusb_configuration_descriptor[2] = {0}; static u8 xusb_status_descriptor[2] = {0}; //gfx_printf("ctrl: %02X %02X %04X %04X %04X\n", _bmRequestType, _bRequest, _wValue, _wIndex, _wLength); // Unhalt EP0 IN. XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_HALT) &= ~XHCI_EP_HALT_DCI_EP0_IN; u32 res = _xusb_xhci_mask_wait(XUSB_DEV_XHCI_EP_HALT, XHCI_EP_HALT_DCI_EP0_IN, 0, 1000); if (res) return res; switch (_bmRequestType) { case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE): if (_bRequest == USB_REQUEST_SET_ADDRESS) _xusb_handle_set_request_dev_address(ctrl_setup); else if (_bRequest == USB_REQUEST_SET_CONFIGURATION) _xusb_handle_set_request_configuration(ctrl_setup); return USB_RES_OK; // What about others. case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_INTERFACE): usbd_xotg->interface_num = _wValue; return _xusb_issue_status_trb(USB_DIR_IN); case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_ENDPOINT): if ((_wValue & 0xFF) == USB_FEATURE_ENDPOINT_HALT) { if (_bRequest == USB_REQUEST_CLEAR_FEATURE || _bRequest == USB_REQUEST_SET_FEATURE) { u32 ep = 0; switch (_wIndex) // endpoint { case USB_EP_ADDR_CTRL_OUT: ep = XUSB_EP_CTRL_OUT; break; case USB_EP_ADDR_CTRL_IN: ep = XUSB_EP_CTRL_IN; break; case USB_EP_ADDR_BULK_OUT: ep = USB_EP_BULK_OUT; break; case USB_EP_ADDR_BULK_IN: ep = USB_EP_BULK_IN; break; default: xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); return USB_RES_OK; } if (_bRequest == USB_REQUEST_CLEAR_FEATURE) xusb_set_ep_stall(ep, USB_EP_CFG_CLEAR); else if (_bRequest == USB_REQUEST_SET_FEATURE) xusb_set_ep_stall(ep, USB_EP_CFG_STALL); return _xusb_issue_status_trb(USB_DIR_IN); } } ep_stall = true; break; case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE): return _xusb_handle_get_class_request(ctrl_setup); case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE): switch (_bRequest) { case USB_REQUEST_GET_STATUS: desc = xusb_dev_status_descriptor; size = sizeof(xusb_dev_status_descriptor); transmit_data = true; break; case USB_REQUEST_GET_DESCRIPTOR: return _xusb_handle_get_descriptor(ctrl_setup); case USB_REQUEST_GET_CONFIGURATION: xusb_configuration_descriptor[0] = usbd_xotg->config_num; desc = xusb_configuration_descriptor; size = sizeof(xusb_configuration_descriptor); transmit_data = true; break; default: ep_stall = true; break; } break; case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_INTERFACE): if (_bRequest == USB_REQUEST_GET_INTERFACE) { desc = xusb_interface_descriptor; size = sizeof(xusb_interface_descriptor); xusb_interface_descriptor[0] = usbd_xotg->interface_num; transmit_data = true; } else if (_bRequest == USB_REQUEST_GET_STATUS) { desc = xusb_status_descriptor; size = sizeof(xusb_status_descriptor); transmit_data = true; } else if (_bRequest == USB_REQUEST_GET_DESCRIPTOR && (_wValue >> 8) == USB_DESCRIPTOR_HID_REPORT && usbd_xotg->gadget > USB_GADGET_UMS) { if (usbd_xotg->gadget == USB_GADGET_HID_GAMEPAD) { desc = (u8 *)&hid_report_descriptor_jc; size = hid_report_descriptor_jc_size; } else // USB_GADGET_HID_TOUCHPAD { desc = (u8 *)&hid_report_descriptor_touch; size = hid_report_descriptor_touch_size; } transmit_data = true; usbd_xotg->device_state = XUSB_HID_CONFIGURED_STS_WAIT; } else ep_stall = true; break; case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_ENDPOINT): if (_bRequest == USB_REQUEST_GET_STATUS) { u32 ep = 0; switch (_wIndex) // endpoint { case USB_EP_ADDR_CTRL_OUT: ep = XUSB_EP_CTRL_OUT; break; case USB_EP_ADDR_CTRL_IN: ep = XUSB_EP_CTRL_IN; break; case USB_EP_ADDR_BULK_OUT: ep = USB_EP_BULK_OUT; break; case USB_EP_ADDR_BULK_IN: ep = USB_EP_BULK_IN; break; default: xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); return USB_RES_OK; } return _xusb_handle_get_ep_status(ep); } ep_stall = true; break; case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE): return _xusb_handle_get_class_request(ctrl_setup); case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_VENDOR | USB_SETUP_RECIPIENT_INTERFACE): case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_VENDOR | USB_SETUP_RECIPIENT_DEVICE): if (_bRequest == USB_REQUEST_GET_MS_DESCRIPTOR) { switch (_wIndex) { case USB_DESCRIPTOR_MS_COMPAT_ID: desc = (u8 *)usbd_xotg->desc->ms_cid; size = usbd_xotg->desc->ms_cid->dLength; transmit_data = true; break; case USB_DESCRIPTOR_MS_EXTENDED_PROPERTIES: desc = (u8 *)usbd_xotg->desc->mx_ext; size = usbd_xotg->desc->mx_ext->dLength; transmit_data = true; break; default: ep_stall = true; break; } } else ep_stall = true; break; default: ep_stall = true; break; } if (transmit_data) { memcpy((u8 *)USB_EP_CONTROL_BUF_ADDR, desc, size); if (_wLength < size) size = _wLength; return _xusb_issue_data_trb((u8 *)USB_EP_CONTROL_BUF_ADDR, size, USB_DIR_IN); } if (ep_stall) xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); return USB_RES_OK; } static int _xusb_ep_operation(u32 tries) { usb_ctrl_setup_t setup_event; volatile event_trb_t *event_trb; setup_event_trb_t *setup_event_trb; // Wait for an interrupt event. int res = _xusb_xhci_mask_wait(XUSB_DEV_XHCI_ST, XHCI_ST_IP, XHCI_ST_IP, tries); if (res) return res; // Clear interrupt status. XUSB_DEV_XHCI(XUSB_DEV_XHCI_ST) |= XHCI_ST_IP; usbd_xotg->event_enqueue_ptr = (event_trb_t *)(XUSB_DEV_XHCI(XUSB_DEV_XHCI_EREPLO) & 0xFFFFFFF0); event_trb = usbd_xotg->event_dequeue_ptr; // Check if cycle matches. if ((event_trb->cycle & 1) != usbd_xotg->event_ccs) return XUSB_ERROR_INVALID_CYCLE; while ((event_trb->cycle & 1) == usbd_xotg->event_ccs) { switch (event_trb->trb_type) { case XUSB_TRB_TRANSFER: res = _xusb_handle_transfer_event((transfer_event_trb_t *)event_trb); break; case XUSB_TRB_PORT_CHANGE: res = _xusb_handle_port_change(); break; case XUSB_TRB_SETUP: setup_event_trb = (setup_event_trb_t *)event_trb; memcpy(&setup_event, &setup_event_trb->ctrl_setup_data, sizeof(usb_ctrl_setup_t)); usbd_xotg->ctrl_seq_num = setup_event_trb->ctrl_seq_num; res = _xusbd_handle_ep0_control_transfer(&setup_event); break; default: // TRB not supported. break; } // Check if last event TRB and reset to first one. if (usbd_xotg->event_dequeue_ptr == &xusb_evtq->xusb_event_ring_seg1[XUSB_LAST_TRB_IDX]) { usbd_xotg->event_dequeue_ptr = xusb_evtq->xusb_event_ring_seg0; usbd_xotg->event_ccs ^= 1; } else // Advance dequeue to next event. usbd_xotg->event_dequeue_ptr = &usbd_xotg->event_dequeue_ptr[1]; // Set next event. event_trb = usbd_xotg->event_dequeue_ptr; // If events exceed the interrupt time, handle them next interrupt. if (usbd_xotg->event_dequeue_ptr == usbd_xotg->event_enqueue_ptr) break; } // Clear Event Handler bit if enabled and set Dequeue pointer. u32 erdp = XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERDPLO) & 0xF; if (erdp & XHCI_ERDPLO_EHB) erdp |= XHCI_ERDPLO_EHB; XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERDPLO) = ((u32)usbd_xotg->event_dequeue_ptr & 0xFFFFFFF0) | erdp; return res; } int xusb_device_enumerate(usb_gadget_type gadget) { switch (gadget) { case USB_GADGET_UMS: usbd_xotg->desc = &usb_gadget_ums_descriptors; break; case USB_GADGET_HID_GAMEPAD: usbd_xotg->desc = &usb_gadget_hid_jc_descriptors; break; case USB_GADGET_HID_TOUCHPAD: usbd_xotg->desc = &usb_gadget_hid_touch_descriptors; break; } usbd_xotg->gadget = gadget; /* * Set interrupt moderation to 0us. * This is important because default value creates a 4.62ms latency. * Effectively hurting transfers by having 15% to 96% performance loss. */ XUSB_DEV_XHCI(XUSB_DEV_XHCI_RT_IMOD) = 0; // Disable Wake events. XUSB_PADCTL(XUSB_PADCTL_ELPG_PROGRAM_0) = 0; XUSB_PADCTL(XUSB_PADCTL_ELPG_PROGRAM_1) = 0; // Enable overrides for VBUS and ID. XUSB_PADCTL(XUSB_PADCTL_USB2_VBUS_ID) = (XUSB_PADCTL(XUSB_PADCTL_USB2_VBUS_ID) & ~(PADCTL_USB2_VBUS_ID_VBUS_OVR_MASK | PADCTL_USB2_VBUS_ID_SRC_MASK)) | PADCTL_USB2_VBUS_ID_VBUS_OVR_EN | PADCTL_USB2_VBUS_ID_SRC_ID_OVR_EN; // Clear halt for LTSSM. XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT) &= ~XHCI_PORTHALT_HALT_LTSSM; // Enable device mode. XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) |= XHCI_CTRL_ENABLE; // Override access to High/Full Speed. XUSB_DEV_XHCI(XUSB_DEV_XHCI_CFG_DEV_FE) = (XUSB_DEV_XHCI(XUSB_DEV_XHCI_CFG_DEV_FE) & ~XHCI_CFG_DEV_FE_PORTREGSEL_MASK) | XHCI_CFG_DEV_FE_PORTREGSEL_HSFS; XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) = (XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) & ~XHCI_PORTSC_PLS_MASK) | XHCI_PORTSC_LWS | XHCI_PORTSC_PLS_RXDETECT; XUSB_DEV_XHCI(XUSB_DEV_XHCI_CFG_DEV_FE) &= ~XHCI_CFG_DEV_FE_PORTREGSEL_MASK; // Enable VBUS and set ID to Float. XUSB_PADCTL(XUSB_PADCTL_USB2_VBUS_ID) = (XUSB_PADCTL(XUSB_PADCTL_USB2_VBUS_ID) & ~PADCTL_USB2_VBUS_ID_OVR_MASK) | PADCTL_USB2_VBUS_ID_OVR_FLOAT | PADCTL_USB2_VBUS_ID_VBUS_ON; usbd_xotg->wait_for_event_trb = XUSB_TRB_SETUP; usbd_xotg->device_state = XUSB_DEFAULT; // Timeout if cable or communication isn't started in 1.5 minutes. u32 timer = get_tmr_ms() + 90000; while (true) { int res = _xusb_ep_operation(USB_XFER_SYNCED_ENUM); // 2s timeout. if (res && res != USB_ERROR_TIMEOUT) return res; if (usbd_xotg->device_state == XUSB_CONFIGURED) break; if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) return USB_ERROR_USER_ABORT; } return USB_RES_OK; } void xusb_end(bool reset_ep, bool only_controller) { // Disable endpoints and stop device mode operation. _xusb_disable_ep1(); // Disable device mode. XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) = 0; //! TODO: Add only controller support? _xusb_device_power_down(); } int xusb_handle_ep0_ctrl_setup() { /* * EP0 Control handling is done by normal ep operation in XUSB. * Here we handle the bulk reset only. */ if (usbd_xotg->bulk_reset_req) { usbd_xotg->bulk_reset_req = false; return USB_RES_BULK_RESET; } return USB_RES_OK; } int xusb_device_ep1_out_read(u8 *buf, u32 len, u32 *bytes_read, u32 sync_tries) { if (len > USB_EP_BUFFER_MAX_SIZE) len = USB_EP_BUFFER_MAX_SIZE; int res = USB_RES_OK; usbd_xotg->tx_count[USB_DIR_OUT] = 0; usbd_xotg->tx_bytes[USB_DIR_OUT] = len; _xusb_issue_normal_trb(buf, len, USB_DIR_OUT); usbd_xotg->tx_count[USB_DIR_OUT]++; if (sync_tries) { while (!res && usbd_xotg->tx_count[USB_DIR_OUT]) res = _xusb_ep_operation(sync_tries); if (bytes_read) *bytes_read = res ? 0 : usbd_xotg->tx_bytes[USB_DIR_OUT]; } // Invalidate data after transfer. bpmp_mmu_maintenance(BPMP_MMU_MAINT_INVALID_WAY, false); return res; } int xusb_device_ep1_out_read_big(u8 *buf, u32 len, u32 *bytes_read) { if (len > USB_EP_BULK_OUT_MAX_XFER) len = USB_EP_BULK_OUT_MAX_XFER; u32 bytes = 0; *bytes_read = 0; u8 *buf_curr = buf; while (len) { u32 len_ep = MIN(len, USB_EP_BUFFER_MAX_SIZE); int res = xusb_device_ep1_out_read(buf_curr, len_ep, &bytes, USB_XFER_SYNCED_DATA); if (res) return res; len -= len_ep; buf_curr += len_ep; *bytes_read = *bytes_read + bytes; } return USB_RES_OK; } int xusb_device_ep1_out_reading_finish(u32 *pending_bytes, u32 sync_tries) { int res = USB_RES_OK; while (!res && usbd_xotg->tx_count[USB_DIR_OUT]) res = _xusb_ep_operation(sync_tries); // Infinite retries. if (pending_bytes) *pending_bytes = res ? 0 : usbd_xotg->tx_bytes[USB_DIR_OUT]; // Invalidate data after transfer. bpmp_mmu_maintenance(BPMP_MMU_MAINT_INVALID_WAY, false); return res; } int xusb_device_ep1_in_write(u8 *buf, u32 len, u32 *bytes_written, u32 sync_tries) { if (len > USB_EP_BUFFER_MAX_SIZE) len = USB_EP_BUFFER_MAX_SIZE; // Flush data before transfer. bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLEAN_WAY, false); int res = USB_RES_OK; usbd_xotg->tx_count[USB_DIR_IN] = 0; usbd_xotg->tx_bytes[USB_DIR_IN] = len; _xusb_issue_normal_trb(buf, len, USB_DIR_IN); usbd_xotg->tx_count[USB_DIR_IN]++; if (sync_tries) { while (!res && usbd_xotg->tx_count[USB_DIR_IN]) res = _xusb_ep_operation(sync_tries); if (bytes_written) *bytes_written = res ? 0 : usbd_xotg->tx_bytes[USB_DIR_IN]; } else { if ((usbd_xotg->port_speed == XUSB_FULL_SPEED && len == 64) || (usbd_xotg->port_speed == XUSB_HIGH_SPEED && len == 512) || (usbd_xotg->port_speed == XUSB_SUPER_SPEED && len == 1024)) { _xusb_issue_normal_trb(buf, 0, USB_DIR_IN); usbd_xotg->tx_count[USB_DIR_IN]++; } } return res; } int xusb_device_ep1_in_writing_finish(u32 *pending_bytes, u32 sync_tries) { int res = USB_RES_OK; while (!res && usbd_xotg->tx_count[USB_DIR_IN]) res = _xusb_ep_operation(sync_tries); // Infinite retries. if (pending_bytes) *pending_bytes = res ? 0 : usbd_xotg->tx_bytes[USB_DIR_IN]; return res; } bool xusb_device_get_port_in_sleep() { // Ejection heuristic. u32 link_mode = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) & XHCI_PORTSC_PLS_MASK; return (link_mode == XHCI_PORTSC_PLS_U3); } bool xusb_device_class_send_max_lun(u8 max_lun) { // Timeout if get MAX_LUN request doesn't happen in 10s. u32 timer = get_tmr_ms() + 10000; usbd_xotg->max_lun = max_lun; usbd_xotg->max_lun_set = true; // Wait for request and transfer start. while (usbd_xotg->device_state != XUSB_LUN_CONFIGURED) { _xusb_ep_operation(USB_XFER_SYNCED_CLASS); if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) return true; } usbd_xotg->device_state = XUSB_CONFIGURED; return false; } bool xusb_device_class_send_hid_report() { // Timeout if get GET_HID_REPORT request doesn't happen in 10s. u32 timer = get_tmr_ms() + 10000; // Wait for request and transfer start. while (usbd_xotg->device_state != XUSB_HID_CONFIGURED) { _xusb_ep_operation(USB_XFER_SYNCED_CLASS); if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) return true; } usbd_xotg->device_state = XUSB_CONFIGURED; return false; } void xusb_device_get_ops(usb_ops_t *ops) { ops->usbd_flush_endpoint = NULL; ops->usbd_set_ep_stall = xusb_set_ep_stall; ops->usbd_handle_ep0_ctrl_setup = xusb_handle_ep0_ctrl_setup; ops->usbd_end = xusb_end; ops->usb_device_init = xusb_device_init; ops->usb_device_enumerate = xusb_device_enumerate; ops->usb_device_class_send_max_lun = xusb_device_class_send_max_lun; ops->usb_device_class_send_hid_report = xusb_device_class_send_hid_report; ops->usb_device_get_suspended = xusb_device_get_port_in_sleep; ops->usb_device_get_port_in_sleep = xusb_device_get_port_in_sleep; ops->usb_device_ep1_out_read = xusb_device_ep1_out_read; ops->usb_device_ep1_out_read_big = xusb_device_ep1_out_read_big; ops->usb_device_ep1_out_reading_finish = xusb_device_ep1_out_reading_finish; ops->usb_device_ep1_in_write = xusb_device_ep1_in_write; ops->usb_device_ep1_in_writing_finish = xusb_device_ep1_in_writing_finish; }