|
@@ -26,19 +26,26 @@
|
|
|
#include <string.h>
|
|
|
|
|
|
#include "console.h"
|
|
|
+#include "usb_hw.h"
|
|
|
#include "usb_priv.h"
|
|
|
+#include "usb.h"
|
|
|
|
|
|
|
|
|
/* Main stack state */
|
|
|
struct usb_stack g_usb;
|
|
|
|
|
|
|
|
|
-/* Helpers for data access */
|
|
|
+/* Helpers */
|
|
|
+/* ------- */
|
|
|
+
|
|
|
+ /* Data buffer access */
|
|
|
+
|
|
|
void
|
|
|
-usb_data_write(int dst_ofs, const void *src, int len)
|
|
|
+usb_data_write(unsigned int dst_ofs, const void *src, int len)
|
|
|
{
|
|
|
+ /* FIXME unaligned ofs */
|
|
|
const uint32_t *src_u32 = src;
|
|
|
- volatile uint32_t *dst_u32 = (volatile uint32_t *)((USB_DATA_BASE) + (dst_ofs << 2));
|
|
|
+ volatile uint32_t *dst_u32 = (volatile uint32_t *)((USB_DATA_BASE) + dst_ofs);
|
|
|
|
|
|
len = (len + 3) >> 2;
|
|
|
while (len--)
|
|
@@ -46,16 +53,17 @@ usb_data_write(int dst_ofs, const void *src, int len)
|
|
|
}
|
|
|
|
|
|
void
|
|
|
-usb_data_read (void *dst, int src_ofs, int len)
|
|
|
+usb_data_read (void *dst, unsigned int src_ofs, int len)
|
|
|
{
|
|
|
- volatile uint32_t *src_u32 = (volatile uint32_t *)((USB_DATA_BASE) + (src_ofs << 2));
|
|
|
+ /* FIXME unaligned ofs */
|
|
|
+ volatile uint32_t *src_u32 = (volatile uint32_t *)((USB_DATA_BASE) + src_ofs);
|
|
|
uint32_t *dst_u32 = dst;
|
|
|
|
|
|
int i = len >> 2;
|
|
|
|
|
|
while (i--)
|
|
|
*dst_u32++ = *src_u32++;
|
|
|
-
|
|
|
+
|
|
|
if ((len &= 3) != 0) {
|
|
|
uint32_t x = *src_u32;
|
|
|
uint8_t *dst_u8 = (uint8_t *)dst_u32;
|
|
@@ -67,36 +75,198 @@ usb_data_read (void *dst, int src_ofs, int len)
|
|
|
}
|
|
|
|
|
|
|
|
|
-/* Debug */
|
|
|
-static const char *_hex = "0123456789abcdef";
|
|
|
+ /* Descriptors */
|
|
|
|
|
|
-static void
|
|
|
-_fast_print_04x(uint32_t v)
|
|
|
+const void *
|
|
|
+usb_desc_find(const void *sod, const void *eod, uint8_t dt)
|
|
|
{
|
|
|
- int i;
|
|
|
- char str[5];
|
|
|
- for (i=3; i>=0; i--) {
|
|
|
- str[i] = _hex[v & 0xf];
|
|
|
- v >>= 4;
|
|
|
+ const uint8_t *sod_p = sod, *eod_p = eod;
|
|
|
+ while ((eod_p - sod_p) >= 2) {
|
|
|
+ if (sod_p[1] == dt)
|
|
|
+ return sod_p;
|
|
|
+ sod_p += sod_p[0];
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+const void *
|
|
|
+usb_desc_next(const void *sod)
|
|
|
+{
|
|
|
+ const uint8_t *sod_p = sod;
|
|
|
+ return sod_p + sod_p[0];
|
|
|
+}
|
|
|
+
|
|
|
+const struct usb_conf_desc *
|
|
|
+usb_desc_find_conf(uint8_t cfg_value)
|
|
|
+{
|
|
|
+ for (int i=0; i<g_usb.stack_desc->n_conf; i++)
|
|
|
+ if (g_usb.stack_desc->conf[i]->bConfigurationValue == cfg_value)
|
|
|
+ return g_usb.stack_desc->conf[i];
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+const struct usb_intf_desc *
|
|
|
+usb_desc_find_intf(const struct usb_conf_desc *conf, uint8_t idx, uint8_t alt,
|
|
|
+ const struct usb_intf_desc **alt0)
|
|
|
+{
|
|
|
+ const struct usb_intf_desc *intf = NULL;
|
|
|
+ const void *sod, *eod;
|
|
|
+
|
|
|
+ /* Config select */
|
|
|
+ if (!conf)
|
|
|
+ conf = g_usb.conf;
|
|
|
+ if (!conf)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ /* Bound the search */
|
|
|
+ sod = conf;
|
|
|
+ eod = sod + conf->wTotalLength;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ sod = usb_desc_find(sod, eod, USB_DT_INTF);
|
|
|
+ if (!sod)
|
|
|
+ break;
|
|
|
+
|
|
|
+ intf = (void*)sod;
|
|
|
+ if (intf->bInterfaceNumber == idx) {
|
|
|
+ if (alt0 && !intf->bAlternateSetting)
|
|
|
+ *alt0 = intf;
|
|
|
+ if (intf->bAlternateSetting == alt)
|
|
|
+ return intf;
|
|
|
+ }
|
|
|
+
|
|
|
+ sod = usb_desc_next(sod);
|
|
|
+ }
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ /* Callback dispatching */
|
|
|
+
|
|
|
+void
|
|
|
+usb_dispatch_sof(void)
|
|
|
+{
|
|
|
+ struct usb_fn_drv *p = g_usb.fnd;
|
|
|
+
|
|
|
+ while (p) {
|
|
|
+ if (p->sof)
|
|
|
+ p->sof();
|
|
|
+ p = p->next;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+usb_dipatch_bus_reset(void)
|
|
|
+{
|
|
|
+ struct usb_fn_drv *p = g_usb.fnd;
|
|
|
+
|
|
|
+ while (p) {
|
|
|
+ if (p->bus_reset)
|
|
|
+ p->bus_reset();
|
|
|
+ p = p->next;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+usb_dispatch_state_chg(enum usb_dev_state state)
|
|
|
+{
|
|
|
+ struct usb_fn_drv *p = g_usb.fnd;
|
|
|
+
|
|
|
+ while (p) {
|
|
|
+ if (p->state_chg)
|
|
|
+ p->state_chg(state);
|
|
|
+ p = p->next;
|
|
|
}
|
|
|
- str[4] = 0;
|
|
|
- puts(str);
|
|
|
}
|
|
|
|
|
|
+enum usb_fnd_resp
|
|
|
+usb_dispatch_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
|
|
|
+{
|
|
|
+ struct usb_fn_drv *p = g_usb.fnd;
|
|
|
+ enum usb_fnd_resp rv = USB_FND_CONTINUE;
|
|
|
+
|
|
|
+ while (p) {
|
|
|
+ if (p->ctrl_req) {
|
|
|
+ rv = p->ctrl_req(req, xfer);
|
|
|
+ if (rv != USB_FND_CONTINUE)
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+ p = p->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ return rv;
|
|
|
+}
|
|
|
+
|
|
|
+enum usb_fnd_resp
|
|
|
+usb_dispatch_set_conf(const struct usb_conf_desc *desc)
|
|
|
+{
|
|
|
+ struct usb_fn_drv *p = g_usb.fnd;
|
|
|
+ enum usb_fnd_resp rv = USB_FND_SUCCESS;
|
|
|
+
|
|
|
+ while (p) {
|
|
|
+ if (p->set_conf) {
|
|
|
+ if (p->set_conf(desc) == USB_FND_ERROR)
|
|
|
+ rv = USB_FND_ERROR;
|
|
|
+ }
|
|
|
+ p = p->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ return rv;
|
|
|
+}
|
|
|
+
|
|
|
+enum usb_fnd_resp
|
|
|
+usb_dispatch_set_intf(const struct usb_intf_desc *base, const struct usb_intf_desc *sel)
|
|
|
+{
|
|
|
+ struct usb_fn_drv *p = g_usb.fnd;
|
|
|
+ enum usb_fnd_resp rv = USB_FND_CONTINUE;
|
|
|
+
|
|
|
+ while (p) {
|
|
|
+ if (p->set_intf) {
|
|
|
+ rv = p->set_intf(base, sel);
|
|
|
+ if (rv != USB_FND_CONTINUE)
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+ p = p->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ return rv;
|
|
|
+}
|
|
|
+
|
|
|
+enum usb_fnd_resp
|
|
|
+usb_dispatch_get_intf(const struct usb_intf_desc *base, uint8_t *sel)
|
|
|
+{
|
|
|
+ struct usb_fn_drv *p = g_usb.fnd;
|
|
|
+ enum usb_fnd_resp rv = USB_FND_CONTINUE;
|
|
|
+
|
|
|
+ while (p) {
|
|
|
+ if (p->get_intf) {
|
|
|
+ rv = p->get_intf(base, sel);
|
|
|
+ if (rv != USB_FND_CONTINUE)
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+ p = p->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ return rv;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/* Debug */
|
|
|
+/* ----- */
|
|
|
+
|
|
|
static void
|
|
|
_fast_print_hex(uint32_t v)
|
|
|
{
|
|
|
- char str[12], *p = str;
|
|
|
+ const char _hex[] = "0123456789abcdef";
|
|
|
int i;
|
|
|
|
|
|
for (i=0; i<4; i++) {
|
|
|
- *p++ = _hex[(v & 0xf0) >> 4];
|
|
|
- *p++ = _hex[ v & 0x0f ];
|
|
|
- *p++ = ' ';
|
|
|
+ putchar(_hex[(v & 0xf0) >> 4]);
|
|
|
+ putchar(_hex[ v & 0x0f ]);
|
|
|
+ putchar(' ');
|
|
|
v >>= 8;
|
|
|
}
|
|
|
- str[11] = 0;
|
|
|
- puts(str);
|
|
|
}
|
|
|
|
|
|
void
|
|
@@ -105,12 +275,12 @@ usb_debug_print_ep(int ep, int dir)
|
|
|
volatile struct usb_ep *ep_regs = dir ? &usb_ep_regs[ep].in : &usb_ep_regs[ep].out;
|
|
|
|
|
|
printf("EP%d %s", ep, dir ? "IN" : "OUT");
|
|
|
- puts("\n\tS "); _fast_print_04x(ep_regs->status);
|
|
|
- puts("\n\tBD0.0 "); _fast_print_04x(ep_regs->bd[0].csr);
|
|
|
- puts("\n\tBD0.1 "); _fast_print_04x(ep_regs->bd[0].ptr);
|
|
|
- puts("\n\tBD1.0 "); _fast_print_04x(ep_regs->bd[1].csr);
|
|
|
- puts("\n\tBD1.1 "); _fast_print_04x(ep_regs->bd[1].ptr);
|
|
|
- puts("\n\n");
|
|
|
+ printf("\tS %04x\n", ep_regs->status);
|
|
|
+ printf("\tBD0.0 %04x\n", ep_regs->bd[0].csr);
|
|
|
+ printf("\tBD0.1 %04x\n", ep_regs->bd[0].ptr);
|
|
|
+ printf("\tBD1.0 %04x\n", ep_regs->bd[1].csr);
|
|
|
+ printf("\tBD1.1 %04x\n", ep_regs->bd[1].ptr);
|
|
|
+ printf("\n");
|
|
|
}
|
|
|
|
|
|
void
|
|
@@ -129,47 +299,261 @@ usb_debug_print_data(int ofs, int len)
|
|
|
void
|
|
|
usb_debug_print(void)
|
|
|
{
|
|
|
- puts("\nCSR:");
|
|
|
- puts("\n\tSR: "); _fast_print_04x(usb_regs->csr);
|
|
|
- puts("\n\n");
|
|
|
+ printf("Stack:\n");
|
|
|
+ printf("\tState: %d\n", g_usb.state);
|
|
|
+ printf("HW:\n");
|
|
|
+ printf("\tSR : %04x\n", usb_regs->csr);
|
|
|
+ printf("\tTick : %04x\n", g_usb.tick);
|
|
|
+ printf("\n");
|
|
|
|
|
|
usb_debug_print_ep(0, 0);
|
|
|
usb_debug_print_ep(0, 1);
|
|
|
- usb_debug_print_ep(1, 0);
|
|
|
- usb_debug_print_ep(1, 1);
|
|
|
|
|
|
- puts("\nData:\n");
|
|
|
+ printf("Data:\n");
|
|
|
usb_debug_print_data(0, 4);
|
|
|
}
|
|
|
|
|
|
|
|
|
+/* Internal API */
|
|
|
+/* ------------ */
|
|
|
+
|
|
|
+static void
|
|
|
+_usb_hw_reset_ep(volatile struct usb_ep *ep)
|
|
|
+{
|
|
|
+ ep->status = 0;
|
|
|
+ ep->bd[0].csr = 0;
|
|
|
+ ep->bd[0].ptr = 0;
|
|
|
+ ep->bd[1].csr = 0;
|
|
|
+ ep->bd[1].ptr = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+_usb_hw_reset(bool pu)
|
|
|
+{
|
|
|
+ /* Clear all descriptors */
|
|
|
+ for (int i=0; i<16; i++) {
|
|
|
+ _usb_hw_reset_ep(&usb_ep_regs[i].out);
|
|
|
+ _usb_hw_reset_ep(&usb_ep_regs[i].in);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Main control */
|
|
|
+ usb_regs->csr = (pu ? USB_CSR_PU_ENA : 0) | USB_CSR_CEL_ENA | USB_CSR_ADDR_MATCH | USB_CSR_ADDR(0);
|
|
|
+ usb_regs->ar = USB_AR_BUS_RST_CLEAR | USB_AR_SOF_CLEAR | USB_AR_CEL_RELEASE;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+usb_bus_reset(void)
|
|
|
+{
|
|
|
+ /* Reset hw */
|
|
|
+ _usb_hw_reset(true);
|
|
|
+
|
|
|
+ /* Reset EP0 */
|
|
|
+ usb_ep0_reset();
|
|
|
+
|
|
|
+ /* Dispatch event */
|
|
|
+ usb_dipatch_bus_reset();
|
|
|
+
|
|
|
+ /* Set state */
|
|
|
+ usb_set_state(USB_DS_DEFAULT);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
/* Exposed API */
|
|
|
+/* ----------- */
|
|
|
|
|
|
void
|
|
|
-usb_init(void)
|
|
|
+usb_init(const struct usb_stack_descriptors *stack_desc)
|
|
|
{
|
|
|
- /* Main state init */
|
|
|
+ /* Main state reset */
|
|
|
memset(&g_usb, 0x00, sizeof(g_usb));
|
|
|
|
|
|
- g_usb.ctrl.state = IDLE;
|
|
|
+ /* Stack setup */
|
|
|
+ g_usb.state = USB_DS_DISCONNECTED;
|
|
|
+ g_usb.stack_desc = stack_desc;
|
|
|
|
|
|
- /* Initialize EP0 */
|
|
|
- usb_ep0_init();
|
|
|
+ usb_register_function_driver(&usb_ctrl_std_drv);
|
|
|
|
|
|
- /* Enable the core */
|
|
|
- usb_regs->csr = USB_CSR_PU_ENA | USB_CSR_CEL_ENA;
|
|
|
+ /* Reset and enable the core */
|
|
|
+ _usb_hw_reset(false);
|
|
|
}
|
|
|
|
|
|
void
|
|
|
usb_poll(void)
|
|
|
{
|
|
|
- uint32_t evt;
|
|
|
+ uint32_t csr;
|
|
|
+
|
|
|
+ /* Active ? */
|
|
|
+ if (g_usb.state < USB_DS_CONNECTED)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /* Read CSR */
|
|
|
+ csr = usb_regs->csr;
|
|
|
+
|
|
|
+ /* Check for pending bus reset */
|
|
|
+ if (csr & USB_CSR_BUS_RST_PENDING) {
|
|
|
+ if (csr & USB_CSR_BUS_RST)
|
|
|
+ return;
|
|
|
+ usb_bus_reset();
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If we've not been reset, only reset is of interest */
|
|
|
+ if (g_usb.state < USB_DS_DEFAULT)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /* Supspend handling */
|
|
|
+ if (csr & USB_CSR_BUS_SUSPEND) {
|
|
|
+ if (!(g_usb.state & USB_DS_SUSPENDED)) {
|
|
|
+ usb_set_state(USB_DS_SUSPENDED);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ } else if (g_usb.state & USB_DS_SUSPENDED) {
|
|
|
+ usb_set_state(USB_DS_RESUME);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* SOF Tick */
|
|
|
+ if (csr & USB_CSR_SOF_PENDING) {
|
|
|
+ g_usb.tick++;
|
|
|
+ usb_regs->ar = USB_AR_SOF_CLEAR;
|
|
|
+ usb_dispatch_sof();
|
|
|
+ }
|
|
|
|
|
|
/* Check for activity */
|
|
|
- evt = usb_regs->evt;
|
|
|
- if (!(evt & 0xf000))
|
|
|
+ if (!(csr & USB_CSR_EVT_PENDING))
|
|
|
+ return;
|
|
|
+ csr = usb_regs->evt;
|
|
|
+
|
|
|
+ /* Poll EP0 (control) */
|
|
|
+ usb_ep0_poll();
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+usb_set_state(enum usb_dev_state new_state)
|
|
|
+{
|
|
|
+ /* Handle resume/suspend 'markers' */
|
|
|
+ if (new_state == USB_DS_RESUME)
|
|
|
+ new_state = g_usb.state & ~USB_DS_SUSPENDED;
|
|
|
+ else if (new_state == USB_DS_SUSPENDED)
|
|
|
+ new_state = g_usb.state | USB_DS_SUSPENDED;
|
|
|
+
|
|
|
+ /* If state is new, update */
|
|
|
+ if (g_usb.state != new_state) {
|
|
|
+ g_usb.state = new_state;
|
|
|
+ usb_dispatch_state_chg(usb_get_state());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+enum usb_dev_state
|
|
|
+usb_get_state(void)
|
|
|
+{
|
|
|
+ return (g_usb.state & USB_DS_SUSPENDED) ? USB_DS_SUSPENDED : g_usb.state;
|
|
|
+}
|
|
|
+
|
|
|
+uint32_t
|
|
|
+usb_get_tick(void)
|
|
|
+{
|
|
|
+ return g_usb.tick;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+usb_connect(void)
|
|
|
+{
|
|
|
+ /* Sanity check */
|
|
|
+ if (g_usb.state != USB_DS_DISCONNECTED)
|
|
|
return;
|
|
|
|
|
|
- /* Run EP0 (control) */
|
|
|
- usb_ep0_run();
|
|
|
+ /* Turn-off pull-up */
|
|
|
+ usb_regs->csr |= USB_CSR_PU_ENA;
|
|
|
+
|
|
|
+ /* Stack update */
|
|
|
+ usb_set_state(USB_DS_CONNECTED);
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+usb_disconnect(void)
|
|
|
+{
|
|
|
+ /* Sanity check */
|
|
|
+ if (g_usb.state < USB_DS_CONNECTED)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /* Turn-off pull-up */
|
|
|
+ usb_regs->csr &= ~USB_CSR_PU_ENA;
|
|
|
+
|
|
|
+ /* Stack state */
|
|
|
+ usb_set_state(USB_DS_DISCONNECTED);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void
|
|
|
+usb_set_address(uint8_t addr)
|
|
|
+{
|
|
|
+ usb_regs->csr = USB_CSR_PU_ENA | USB_CSR_CEL_ENA | USB_CSR_ADDR_MATCH | USB_CSR_ADDR(addr);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void
|
|
|
+usb_register_function_driver(struct usb_fn_drv *drv)
|
|
|
+{
|
|
|
+ drv->next = g_usb.fnd;
|
|
|
+ g_usb.fnd = drv;
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+usb_unregister_function_driver(struct usb_fn_drv *drv)
|
|
|
+{
|
|
|
+ struct usb_fn_drv **p = &g_usb.fnd;
|
|
|
+ while (*p) {
|
|
|
+ if (*p == drv) {
|
|
|
+ *p = drv->next;
|
|
|
+ drv->next = NULL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ p = &(*p)->next->next;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static volatile struct usb_ep *
|
|
|
+_get_ep_regs(uint8_t ep)
|
|
|
+{
|
|
|
+ return (ep & 0x80) ?
|
|
|
+ &usb_ep_regs[ep & 0xf].in :
|
|
|
+ &usb_ep_regs[ep & 0xf].out;
|
|
|
+}
|
|
|
+
|
|
|
+bool
|
|
|
+usb_ep_is_configured(uint8_t ep)
|
|
|
+{
|
|
|
+ volatile struct usb_ep *epr = _get_ep_regs(ep);
|
|
|
+ uint32_t s = epr->status;
|
|
|
+ return USB_EP_TYPE(s) != USB_EP_TYPE_NONE;
|
|
|
+}
|
|
|
+
|
|
|
+bool
|
|
|
+usb_ep_is_halted(uint8_t ep)
|
|
|
+{
|
|
|
+ volatile struct usb_ep *epr = _get_ep_regs(ep);
|
|
|
+ uint32_t s = epr->status;
|
|
|
+ return USB_EP_TYPE_IS_BCI(s) && (s & USB_EP_TYPE_HALTED);
|
|
|
+}
|
|
|
+
|
|
|
+bool
|
|
|
+usb_ep_halt(uint8_t ep)
|
|
|
+{
|
|
|
+ volatile struct usb_ep *epr = _get_ep_regs(ep);
|
|
|
+ uint32_t s = epr->status;
|
|
|
+ if (!USB_EP_TYPE_IS_BCI(s))
|
|
|
+ return false;
|
|
|
+ epr->status = s | USB_EP_TYPE_HALTED;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool
|
|
|
+usb_ep_resume(uint8_t ep)
|
|
|
+{
|
|
|
+ volatile struct usb_ep *epr = _get_ep_regs(ep);
|
|
|
+ uint32_t s = epr->status;
|
|
|
+ if (!USB_EP_TYPE_IS_BCI(s))
|
|
|
+ return false;
|
|
|
+ epr->status = s & ~(USB_EP_TYPE_HALTED | USB_EP_DT_BIT); /* DT bit clear needed by CLEAR_FEATURE */
|
|
|
+ return true;
|
|
|
}
|