hekate-emmc/bdk/usb/xusbd.c
CTCaer d08fac5a08 bdk: xusb: improve clock deinit
Allows L4T to use XUSB on T210B01 after a UMS usage.
T210 somehow was fine.
2022-10-11 04:07:24 +03:00

2210 lines
61 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <usb/usbd.h>
#include <usb/usb_descriptor_types.h>
#include <usb/usb_t210.h>
#include <gfx_utils.h>
#include <mem/mc.h>
#include <soc/bpmp.h>
#include <soc/clock.h>
#include <soc/fuse.h>
#include <soc/pmc.h>
#include <soc/timer.h>
#include <soc/t210.h>
#include <utils/btn.h>
#include <memory_map.h>
#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;
}