summaryrefslogtreecommitdiff
path: root/library/ADK2/usbh.c
diff options
context:
space:
mode:
Diffstat (limited to 'library/ADK2/usbh.c')
-rw-r--r--library/ADK2/usbh.c1197
1 files changed, 1197 insertions, 0 deletions
diff --git a/library/ADK2/usbh.c b/library/ADK2/usbh.c
new file mode 100644
index 0000000..9123ba0
--- /dev/null
+++ b/library/ADK2/usbh.c
@@ -0,0 +1,1197 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define ADK_INTERNAL
+#include "Arduino.h"
+#include "usbh.h"
+#include "coop.h"
+#include "conf_usb.h"
+#include "usb_drv.h"
+#include "usb_ids.h"
+
+#define USE_HIGH_SPEED 0
+#define AUDIO_ACCESSORY 1
+
+#define PIPE_FLAG_INCOMPLETE_SETUP 0x1
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+
+typedef struct usbh_pipe {
+ volatile enum pipe_state {
+ PIPE_NO_CONFIG,
+ PIPE_IDLE,
+ PIPE_SETUP_DTOH,
+ PIPE_SETUP_DTOH_IN,
+ PIPE_SETUP_DTOH_OUT,
+ PIPE_SETUP_HTOD,
+ PIPE_SETUP_HTOD_OUT,
+ PIPE_SETUP_HTOD_IN,
+ PIPE_SETUP_COMPLETE,
+ PIPE_IN,
+ PIPE_IN_COMPLETE,
+ PIPE_OUT,
+ PIPE_OUT_COMPLETE,
+ PIPE_ISO_IN,
+ PIPE_ISO_IN_WAIT,
+ } state;
+ uint32_t flags;
+ uint8_t endp;
+ enum usbh_endp_type type;
+
+ /* current transfer stats */
+ void *ptr;
+ size_t pos;
+ size_t total_len;
+ int result;
+
+ /* isochronous transfers */
+ usbh_iso_callback_t iso_cb;
+ void *iso_cb_arg;
+} usbh_pipe_t;
+
+typedef struct usbh_state {
+ enum {
+ USBH_DISABLED,
+ USBH_INIT,
+ USBH_DEVICE_UNATTACHED,
+ USBH_WAIT_FOR_DEVICE,
+ USBH_DEVICE_ATTACHED,
+ USBH_DEVICE_ATTACHED_SOF_WAIT,
+ USBH_DEVICE_ATTACHED_RESET,
+ USBH_DEVICE_ATTACHED_RESET_WAIT,
+ USBH_DEVICE_ATTACHED_POST_RESET_WAIT,
+ USBH_DEVICE_ATTACHED_QUERY,
+ USBH_DEVICE_TRY_ACCESSORY,
+ USBH_DEVICE_ACCESSORY_INIT,
+ USBH_DEVICE_ACCESSORY,
+ USBH_DEVICE_IDLE,
+ } state;
+ uint8_t next_address;
+ uint32_t last_sof;
+ uint32_t irq_count;
+
+ usbh_device_t dev;
+
+ usbh_pipe_t pipe[CHIP_USB_NUMENDPOINTS];
+
+ uint64_t sleep_ts;
+
+
+ // accessory strings
+ const char *accessory_string_vendor;
+ const char *accessory_string_name;
+ const char *accessory_string_longname;
+ const char *accessory_string_version;
+ const char *accessory_string_url;
+ const char *accessory_string_serial;
+} usbh_state_t;
+
+/* master state for the stack */
+static usbh_state_t usbh;
+
+static const char *pipe_state_to_str(enum pipe_state state)
+{
+#define STATE2STR(x) case x: return #x;
+
+ switch (state) {
+ STATE2STR(PIPE_NO_CONFIG)
+ STATE2STR(PIPE_IDLE)
+ STATE2STR(PIPE_SETUP_DTOH)
+ STATE2STR(PIPE_SETUP_DTOH_IN)
+ STATE2STR(PIPE_SETUP_DTOH_OUT)
+ STATE2STR(PIPE_SETUP_HTOD)
+ STATE2STR(PIPE_SETUP_HTOD_OUT)
+ STATE2STR(PIPE_SETUP_HTOD_IN)
+ STATE2STR(PIPE_SETUP_COMPLETE)
+ STATE2STR(PIPE_IN)
+ STATE2STR(PIPE_IN_COMPLETE)
+ STATE2STR(PIPE_OUT)
+ STATE2STR(PIPE_OUT_COMPLETE)
+ default: return "unknown";
+ }
+
+#undef STATE2STR
+}
+
+static void dump_pipe(int pipe)
+{
+ const usbh_pipe_t *p = &usbh.pipe[pipe];
+
+ TRACE_OTG("pipe %d: state %d (%s) flags 0x%x ptr 0x%x pos %u total_len %u\n",
+ pipe, p->state, pipe_state_to_str(p->state), p->flags, p->ptr, p->pos, p->total_len);
+}
+
+static void usbh_start_sleep(void)
+{
+ usbh.sleep_ts = millis();
+}
+
+static bool usbh_sleep_expired(uint32_t ms)
+{
+ uint64_t now = millis();
+
+ if (now - usbh.sleep_ts >= ms)
+ return true;
+ else
+ return false;
+}
+
+int usbh_setup_endpoint(uint8_t addr, uint8_t endp, enum usbh_endp_type type, size_t packet_size)
+{
+ // find a free pipe
+ int pipe;
+ for (pipe = 0; pipe < CHIP_USB_NUMENDPOINTS; pipe++) {
+ if (usbh.pipe[pipe].state == PIPE_NO_CONFIG)
+ break;
+ }
+ if (pipe >= CHIP_USB_NUMENDPOINTS)
+ return -1;
+
+ usbh_pipe_t *p = &usbh.pipe[pipe];
+
+ p->state = PIPE_IDLE;
+ p->endp = endp;
+ p->type = type;
+
+ // set up hardware
+ uint32_t token = (endp & USB_ENDPOINT_IN) ? TOKEN_IN : TOKEN_OUT;
+ switch (type) {
+ default:
+ case ENDP_TYPE_CONTROL:
+ Host_configure_pipe(pipe, 0, endp, TYPE_CONTROL, TOKEN_SETUP, packet_size, SINGLE_BANK);
+ break;
+ case ENDP_TYPE_BULK:
+ Host_configure_pipe(pipe, 0, endp, TYPE_BULK, token, packet_size, SINGLE_BANK);
+ break;
+ case ENDP_TYPE_INT:
+ Host_configure_pipe(pipe, 0, endp, TYPE_INTERRUPT, token, packet_size, SINGLE_BANK);
+ break;
+ case ENDP_TYPE_ISO:
+ Host_configure_pipe(pipe, 0, endp, TYPE_ISOCHRONOUS, token, packet_size, SINGLE_BANK);
+ break;
+ }
+ Host_configure_address(pipe, addr);
+ Host_enable_pipe_interrupt(pipe);
+
+ return pipe;
+}
+
+int usbh_free_endpoint(int pipe)
+{
+ usbh_pipe_t *p = &usbh.pipe[pipe];
+
+ Host_disable_pipe_interrupt(pipe);
+ Host_reset_pipe(pipe);
+ Host_unallocate_memory(pipe);
+ Host_disable_pipe(pipe);
+
+ memset(p, 0, sizeof(usbh_pipe_t));
+
+ return 0;
+}
+
+static void usbh_start_iso_transfer(int pipe)
+{
+ usbh_pipe_t *p = &usbh.pipe[pipe];
+
+ ASSERT(p->type == ENDP_TYPE_ISO);
+
+ if (p->endp & USB_ENDPOINT_IN) {
+ p->state = PIPE_ISO_IN;
+ Disable_global_interrupt();
+ Host_reset_pipe(pipe);
+ UOTGHS->UOTGHS_HSTPIPERR[pipe] = 0;
+ Host_ack_in_received(pipe); Host_enable_in_received_interrupt(pipe);
+ Host_ack_pipe_error(pipe); Host_enable_pipe_error_interrupt(pipe);
+ Enable_global_interrupt();
+ Host_disable_continuous_in_mode(pipe);
+ Host_configure_pipe_token(pipe, TOKEN_IN);
+ Host_ack_in_received(pipe);
+ Host_unfreeze_pipe(pipe);
+ } else {
+ panic("unsupported iso out\n");
+ }
+}
+
+int usbh_queue_iso_transfer(int pipe, void *buf, size_t buflen, usbh_iso_callback_t cb, void *arg)
+{
+ usbh_pipe_t *p = &usbh.pipe[pipe];
+
+// TRACE_OTG("pipe %d buf %p buflen %d cb %p arg %p\n", pipe, buf, buflen, cb, arg);
+
+ ASSERT(p->type == ENDP_TYPE_ISO);
+ ASSERT(p->state == PIPE_IDLE);
+
+ p->ptr = buf;
+ p->total_len = buflen;
+ p->pos = 0;
+
+ /* iso specific bits */
+ p->iso_cb = cb;
+ p->iso_cb_arg = arg;
+
+ ASSERT(p->endp & USB_ENDPOINT_IN);
+
+ usbh_start_iso_transfer(pipe);
+
+ return 0;
+}
+
+int usbh_set_iso_buffer(int pipe, void *buf, size_t buflen)
+{
+ usbh_pipe_t *p = &usbh.pipe[pipe];
+
+ ASSERT(p->type == ENDP_TYPE_ISO);
+
+ p->ptr = buf;
+ p->total_len = buflen;
+ p->pos = 0;
+
+ return 0;
+}
+
+int usbh_queue_transfer(int pipe, void *buf, size_t len)
+{
+ usbh_pipe_t *p = &usbh.pipe[pipe];
+
+ //TRACE_OTG("pipe %d, endp 0x%x, buf %p, len 0x%x\n", pipe, p->endp, buf, len);
+
+ ASSERT(p->state == PIPE_IDLE);
+
+ p->ptr = buf;
+ p->total_len = len;
+ p->pos = 0;
+
+ if (p->type == ENDP_TYPE_BULK || p->type == ENDP_TYPE_ISO) {
+ if (p->endp & USB_ENDPOINT_IN) {
+ p->state = PIPE_IN;
+ Disable_global_interrupt();
+ Host_reset_pipe(pipe);
+ UOTGHS->UOTGHS_HSTPIPERR[pipe] = 0;
+ Host_ack_in_received(pipe); Host_enable_in_received_interrupt(pipe);
+ Host_ack_nak_received(pipe); Host_enable_nak_received_interrupt(pipe);
+ //Host_ack_short_packet(pipe); Host_enable_short_packet_interrupt(pipe);
+ Host_ack_pipe_error(pipe); Host_enable_pipe_error_interrupt(pipe);
+ Enable_global_interrupt();
+ Host_disable_continuous_in_mode(pipe);
+ Host_configure_pipe_token(pipe, TOKEN_IN);
+ Host_ack_in_received(pipe);
+ Host_unfreeze_pipe(pipe);
+ } else {
+ p->state = PIPE_OUT;
+ Disable_global_interrupt();
+ Host_reset_pipe(pipe);
+ Host_configure_pipe_token(pipe, TOKEN_OUT);
+ Host_ack_out_ready(pipe); Host_enable_out_ready_interrupt(pipe);
+ Host_ack_pipe_error(pipe); Host_enable_pipe_error_interrupt(pipe);
+ //Host_enable_ping(pipe); // XXX ONLY for usb 2.0
+
+ Host_unfreeze_pipe(pipe);
+ Enable_global_interrupt();
+
+ Host_reset_pipe_fifo_access(pipe);
+
+ size_t towrite = MIN(p->total_len, Host_get_pipe_size(pipe));
+ uint32_t written = host_write_p_txpacket(pipe, p->ptr, towrite, NULL);
+ p->pos += written;
+
+ Host_ack_out_ready(pipe);
+ Host_send_out(pipe);
+ }
+ } else {
+ panic("unimplemented\n");
+ }
+#if 0
+ dbgPrintf("INRQ 0x%x\n", UOTGHS->UOTGHS_HSTPIPINRQ[pipe]);
+ dbgPrintf("PIPCFG 0x%x\n", UOTGHS->UOTGHS_HSTPIPCFG[pipe]);
+ dbgPrintf("PIPIMR 0x%x\n", UOTGHS->UOTGHS_HSTPIPIMR[pipe]);
+ dbgPrintf("PIPERR 0x%x\n", UOTGHS->UOTGHS_HSTPIPERR[pipe]);
+ dbgPrintf("PIP 0x%x\n", UOTGHS->UOTGHS_HSTPIP);
+#endif
+
+ return 0;
+}
+
+int usbh_check_transfer(int pipe)
+{
+ usbh_pipe_t *p = &usbh.pipe[pipe];
+
+ // poll the pipe to wait for it to get to complete
+ int result;
+ switch (p->state) {
+ case PIPE_IN_COMPLETE:
+ case PIPE_OUT_COMPLETE:
+ p->state = PIPE_IDLE;
+ if (p->result < 0) {
+ result = p->result;
+ } else {
+ result = p->pos;
+ }
+ p->result = PIPE_RESULT_OK;
+ break;
+ case PIPE_IN:
+ case PIPE_OUT:
+ result = PIPE_RESULT_NOT_READY;
+ break;
+ default:
+ case PIPE_NO_CONFIG:
+ case PIPE_IDLE:
+ result = PIPE_RESULT_NOT_QUEUED;
+ break;
+ }
+
+ return result;
+}
+
+uint32_t usbh_get_frame_num(void)
+{
+ return (UOTGHS->UOTGHS_HSTFNUM >> 3) & 0x7ff;
+}
+
+static void usbh_host_reset_pipe(int pipe)
+{
+ usbh_pipe_t *p = &usbh.pipe[pipe];
+
+ switch (p->state) {
+ case PIPE_SETUP_DTOH:
+ case PIPE_SETUP_DTOH_IN:
+ case PIPE_SETUP_DTOH_OUT:
+ case PIPE_SETUP_HTOD:
+ case PIPE_SETUP_HTOD_OUT:
+ case PIPE_SETUP_HTOD_IN:
+ case PIPE_SETUP_COMPLETE:
+ p->state = PIPE_SETUP_COMPLETE;
+ p->result = PIPE_RESULT_RESET;
+ break;
+ default:
+ p->state = PIPE_IDLE;
+ ;
+ }
+}
+
+static void usbh_reset_all_pipes(void)
+{
+ int i;
+ for (i = 0; i < CHIP_USB_NUMENDPOINTS; i++) {
+ usbh_host_reset_pipe(i);
+ }
+}
+
+static void usbh_clear_all_pipes(void)
+{
+ int i;
+ for (i = 0; i < CHIP_USB_NUMENDPOINTS; i++) {
+ usbh_free_endpoint(i);
+ }
+}
+
+static void usbh_host_pipe_irq(int pipe)
+{
+ usbh_pipe_t *p = &usbh.pipe[pipe];
+
+ uint32_t pipe_isr = UOTGHS->UOTGHS_HSTPIPISR[pipe];
+// TRACE_OTG("pipe isr 0x%x\n", pipe_isr);
+ pipe_isr &= UOTGHS->UOTGHS_HSTPIPIMR[pipe];
+// TRACE_OTG("pipe %d isr 0x%x (post mask)\n", pipe, pipe_isr);
+// dump_pipe(pipe);
+
+ if (pipe_isr & UOTGHS_HSTPIPISR_TXSTPI) { // transmitted setup
+ Host_ack_setup_ready();
+ Host_disable_setup_ready_interrupt();
+ if (p->state == PIPE_SETUP_DTOH) {
+ if (p->total_len > 0) {
+ // deal with the IN token
+ Host_disable_continuous_in_mode(pipe);
+ Host_configure_pipe_token(pipe, TOKEN_IN);
+ Host_ack_in_received_free(pipe); Host_enable_in_received_interrupt(pipe);
+ Host_ack_stall(pipe); Host_enable_stall_interrupt(pipe);
+ Host_unfreeze_pipe(pipe);
+
+ p->state = PIPE_SETUP_DTOH_IN;
+ } else {
+ panic("unhandled situation\n");
+ p->state = PIPE_SETUP_DTOH_OUT;
+ }
+ } else if (p->state == PIPE_SETUP_HTOD) {
+ if (p->total_len > 0) {
+ // out token phase
+ Host_configure_pipe_token(pipe, TOKEN_OUT);
+ Host_ack_out_ready(pipe); Host_enable_out_ready_interrupt(pipe);
+ Host_ack_stall(pipe); Host_enable_stall_interrupt(pipe);
+
+ Host_unfreeze_pipe(pipe);
+
+ Host_reset_pipe_fifo_access(pipe);
+ uint32_t written = host_write_p_txpacket(pipe, p->ptr, p->total_len, NULL);
+ //TRACE_OTG("%d bytes written to packet\n", written);
+ p->pos += written;
+
+ Host_send_out(pipe);
+
+// panic("unhandled situation: out phase with bytes\n");
+ p->state = PIPE_SETUP_HTOD_OUT;
+ } else {
+ // deal with the IN token
+ Host_disable_continuous_in_mode(pipe);
+ Host_configure_pipe_token(pipe, TOKEN_IN);
+ Host_ack_in_received_free(pipe); Host_enable_in_received_interrupt(pipe);
+ Host_ack_stall(pipe); Host_enable_stall_interrupt(pipe);
+ Host_unfreeze_pipe(pipe);
+
+ p->state = PIPE_SETUP_HTOD_IN;
+ }
+ } else {
+ panic("bad state %d (%s) with TXSTPI\n", p->state, pipe_state_to_str(p->state));
+ }
+ }
+ if (pipe_isr & UOTGHS_HSTPIPISR_RXINI) { // in packet
+ if (p->state == PIPE_SETUP_DTOH_IN) {
+ //TRACE_OTG("got in, ");
+
+ Host_reset_pipe_fifo_access(pipe);
+
+ uint32_t read = host_read_p_rxpacket(pipe, (uint8_t *)p->ptr + p->pos, p->total_len - p->pos, NULL);
+ p->pos += read;
+
+ //TRACE_OTG_NONL("read %d\n", read);
+
+ Host_freeze_pipe(pipe);
+
+ if ((p->flags & PIPE_FLAG_INCOMPLETE_SETUP) || read < Host_get_pipe_size(pipe) || p->pos == p->total_len) {
+ // deal with OUT token
+ //TRACE_OTG("done, moving to OUT phase\n");
+ Host_ack_in_received(pipe);
+ Host_disable_in_received_interrupt(pipe);
+ Host_configure_pipe_token(pipe, TOKEN_OUT);
+ Host_ack_out_ready_send(pipe); Host_enable_out_ready_interrupt(pipe);
+ Host_ack_stall(pipe); Host_enable_stall_interrupt(pipe);
+ Host_unfreeze_pipe(pipe);
+ p->state = PIPE_SETUP_DTOH_OUT;
+ } else {
+ Host_ack_in_received_free(pipe);
+ Host_unfreeze_pipe(pipe);
+ }
+ } else if (p->state == PIPE_SETUP_HTOD_IN) {
+ Host_free_in(pipe);
+ Host_disable_in_received_interrupt(pipe);
+ p->state = PIPE_SETUP_COMPLETE;
+ //TRACE_OTG("end of in, done\n");
+ } else if (p->state == PIPE_IN) {
+ // regular pipe in in mode
+ Host_ack_in_received(pipe);
+
+ //TRACE_OTG("got in, ");
+
+ Host_reset_pipe_fifo_access(pipe);
+ uint32_t read = host_read_p_rxpacket(pipe, (uint8_t *)p->ptr + p->pos, p->total_len - p->pos, NULL);
+ p->pos += read;
+
+ //TRACE_OTG_NONL("read %d\n", read);
+
+ Host_freeze_pipe(pipe);
+
+ if (read < Host_get_pipe_size(pipe) || p->pos == p->total_len) {
+ Host_reset_pipe(pipe);
+ p->state = PIPE_IN_COMPLETE;
+ p->result = PIPE_RESULT_OK;
+ } else {
+ // multi in transfer
+ //Host_configure_pipe_token(pipe, TOKEN_IN);
+ Host_ack_in_received_free(pipe);
+ Host_unfreeze_pipe(pipe);
+ }
+ } else if (p->state == PIPE_ISO_IN) {
+ // isochronous pipe in in mode
+ Host_ack_in_received(pipe);
+
+ //TRACE_OTG("ISO got in, ");
+
+ Host_reset_pipe_fifo_access(pipe);
+ uint32_t read = host_read_p_rxpacket(pipe, (uint8_t *)p->ptr + p->pos, p->total_len - p->pos, NULL);
+ p->pos += read;
+
+ //TRACE_OTG_NONL("read %d\n", read);
+
+ Host_freeze_pipe(pipe);
+
+ p->state = PIPE_ISO_IN_WAIT;
+ p->result = PIPE_RESULT_OK;
+
+ Host_ack_in_received(pipe);
+
+ // schedule for a new sof interrupt
+ Host_enable_sof_interrupt();
+ usbh.last_sof = (UOTGHS->UOTGHS_HSTFNUM >> 3) & 0x7ff;
+
+ // do callback
+ p->iso_cb(p->iso_cb_arg, p->result, p->ptr, p->pos);
+ } else {
+ panic("bad state %d (%s) with RXINIT\n", p->state, pipe_state_to_str(p->state));
+ }
+ }
+ if (pipe_isr & UOTGHS_HSTPIPISR_TXOUTI) { // out packet complete
+ if (p->state == PIPE_SETUP_DTOH_OUT) {
+ Host_disable_out_ready_interrupt(pipe);
+ Host_ack_out_ready(pipe);
+ p->state = PIPE_SETUP_COMPLETE;
+ p->result = PIPE_RESULT_OK;
+ //TRACE_OTG("end of out, done\n");
+ } else if (p->state == PIPE_SETUP_HTOD_OUT) {
+ if (p->pos == p->total_len) {
+ // deal with the IN token
+ Host_disable_out_ready_interrupt(pipe);
+ Host_ack_out_ready(pipe);
+ Host_disable_continuous_in_mode(pipe);
+ Host_configure_pipe_token(pipe, TOKEN_IN);
+ Host_ack_in_received_free(pipe); Host_enable_in_received_interrupt(pipe);
+ Host_ack_stall(pipe); Host_enable_stall_interrupt(pipe);
+ Host_unfreeze_pipe(pipe);
+ p->state = PIPE_SETUP_HTOD_IN;
+ } else {
+ panic("unhandled situation, multi out control transfer\n");
+ Host_ack_out_ready(pipe);
+ Host_unfreeze_pipe(pipe);
+ }
+ } else if (p->state == PIPE_OUT) {
+ // regular pipe in out mode
+ Host_ack_out_ready(pipe);
+ if (p->pos == p->total_len) {
+ //TRACE_OTG("OUT complete\n");
+ Host_reset_pipe(pipe);
+ p->state = PIPE_OUT_COMPLETE;
+ p->result = PIPE_RESULT_OK;
+ } else {
+ //Host_reset_pipe(pipe);
+ Host_configure_pipe_token(pipe, TOKEN_OUT);
+ Host_ack_out_ready(pipe); Host_enable_out_ready_interrupt(pipe);
+ Host_ack_pipe_error(pipe); Host_enable_pipe_error_interrupt(pipe);
+
+ Host_unfreeze_pipe(pipe);
+
+ Host_reset_pipe_fifo_access(pipe);
+ size_t towrite = MIN(p->total_len - p->pos, Host_get_pipe_size(pipe));
+ //TRACE_OTG("second part of out: towrite %d pos %d\n", towrite, p->pos);
+ uint32_t written = host_write_p_txpacket(pipe, p->ptr + p->pos, towrite, NULL);
+ p->pos += written;
+
+ Host_ack_out_ready(pipe);
+ Host_send_out(pipe);
+ }
+ } else {
+ panic("bad state %d (%s) with TXOUTI\n", p->state, pipe_state_to_str(p->state));
+ }
+ }
+ if (pipe_isr & UOTGHS_HSTPIPISR_NAKEDI) { // nak received
+ //TRACE_OTG("pipe %d got nak\n", pipe);
+ Host_ack_nak_received(pipe);
+ Host_disable_nak_received_interrupt(pipe);
+ if (p->state == PIPE_IN) {
+ Host_reset_pipe(pipe);
+ p->state = PIPE_IN_COMPLETE;
+ p->result = PIPE_RESULT_NAK;
+ } if (p->state == PIPE_IN_COMPLETE) {
+ ; // do nothing
+ } else {
+ panic("bad state %d (%s) with NAKEDI\n", p->state, pipe_state_to_str(p->state));
+ }
+ }
+ if (pipe_isr & UOTGHS_HSTPIPISR_RXSTALLDI) { // stall received
+ Host_disable_stall_interrupt(pipe);
+ Host_ack_stall(pipe);
+ if (p->state == PIPE_SETUP_DTOH_IN) {
+ Host_disable_in_received_interrupt(pipe);
+ p->state = PIPE_SETUP_COMPLETE;
+ p->result = PIPE_RESULT_STALL;
+ } else if (p->state == PIPE_SETUP_HTOD_IN) {
+ Host_disable_in_received_interrupt(pipe);
+ p->state = PIPE_SETUP_COMPLETE;
+ p->result = PIPE_RESULT_STALL;
+ } else {
+ panic("bad state %d (%s) with RXSTALLDI\n", p->state, pipe_state_to_str(p->state));
+ }
+ }
+ if (pipe_isr & UOTGHS_HSTPIPISR_PERRI) { // pipe error
+ uint32_t perr = UOTGHS->UOTGHS_HSTPIPERR[pipe];
+ TRACE_OTG("pipe %d got perr 0x%x\n", pipe, perr);
+
+ Host_ack_pipe_error(pipe);
+
+ if (p->state == PIPE_IN && perr & UOTGHS_HSTPIPERR_PID) {
+ UOTGHS->UOTGHS_HSTPIPERR[pipe] = ~0x64;
+
+ panic("got PID error\n");
+
+ // XXX why do we get pid error?
+ Host_unfreeze_pipe(pipe);
+ }
+ }
+}
+
+static void usbh_host_irq(uint32_t isr)
+{
+ int i;
+
+ if (isr & UOTGHS_HSTISR_DCONNI) { // device connected
+ dbgPrintf("USB: connect\n");
+ }
+ if (isr & UOTGHS_HSTISR_DDISCI) { // device disconnected
+ dbgPrintf("USB: disconnect\n");
+ usbh.state = USBH_DEVICE_UNATTACHED;
+ host_disable_all_pipes();
+ usbh_reset_all_pipes();
+ goto done;
+ }
+ if (isr & UOTGHS_HSTISR_HSOFI) { // start of frame
+ Host_ack_sof();
+
+ //uint32_t f = (UOTGHS->UOTGHS_HSTFNUM >> 3) & 0x7ff;
+ //if (usbh.last_sof != f) {
+ for (i = 0; i < CHIP_USB_NUMENDPOINTS; i++) {
+ if (usbh.pipe[i].state == PIPE_ISO_IN_WAIT) {
+ //dbgPrintf("SOF: iso in wait pipe %d\n", i);
+ //dbgPrintf("last 0x%x f 0x%x\n", last_sof, f);
+ Host_disable_sof_interrupt();
+
+ usbh_start_iso_transfer(i);
+ }
+ }
+ //}
+ }
+
+ for (i = 0; i < CHIP_USB_NUMENDPOINTS; i++) {
+ if (isr & (1 << (8 + i)))
+ usbh_host_pipe_irq(i);
+ }
+
+done:
+ UOTGHS->UOTGHS_HSTICR = isr;
+}
+
+void UOTGHS_Handler(void)
+{
+ usbh.irq_count++;
+
+#if 0
+ if ((usbh.irq_count % 1024) == 0)
+ TRACE_OTG("%d usb irqs\n", usbh.irq_count);
+#endif
+
+ uint32_t gen_sr = UOTGHS->UOTGHS_SR;
+ uint32_t host_isr = UOTGHS->UOTGHS_HSTISR;
+ host_isr &= UOTGHS->UOTGHS_HSTIMR;
+
+ if (host_isr) {
+ usbh_host_irq(host_isr);
+ }
+}
+
+int usbh_send_setup(const void *_setup, void *buf, int incomplete)
+{
+ const struct usb_setup_packet *setup = _setup;
+
+// TRACE_OTG("setup %p, buf %p, incomplete %d\n", setup, buf, incomplete);
+ ASSERT(usbh.pipe[P_CONTROL].state == PIPE_IDLE);
+
+ Disable_global_interrupt();
+ Host_configure_pipe_token(P_CONTROL, TOKEN_SETUP);
+ Host_ack_setup_ready();
+ Host_unfreeze_pipe(P_CONTROL);
+ Host_enable_setup_ready_interrupt();
+
+ Host_reset_pipe_fifo_access(P_CONTROL);
+ host_write_p_txpacket(P_CONTROL, setup, sizeof(*setup), NULL);
+ Host_send_setup();
+
+ /* set up pipe state */
+ usbh_pipe_t *p = &usbh.pipe[P_CONTROL];
+ p->state = (setup->bmRequestType & USB_SETUP_DIR_DEVICE_TO_HOST) ? PIPE_SETUP_DTOH : PIPE_SETUP_HTOD;
+ p->flags = incomplete ? PIPE_FLAG_INCOMPLETE_SETUP : 0;
+ p->ptr = buf;
+ p->pos = 0;
+ p->total_len = setup->wLength;
+
+ Enable_global_interrupt();
+
+ // XXX timeout
+ uint64_t t = micros();
+ int result = 0;
+ for (;;) {
+ coopYield();
+
+ ASSERT(p->state != PIPE_NO_CONFIG);
+ ASSERT(p->state != PIPE_IDLE);
+
+ // poll the pipe to wait for it to get to complete
+ if (p->state == PIPE_SETUP_COMPLETE) {
+ p->state = PIPE_IDLE;
+ if (p->result < 0) {
+ result = p->result;
+ } else {
+ result = p->pos;
+ }
+ p->result = PIPE_RESULT_OK;
+ break;
+ }
+ }
+ //TRACE_OTG("transfer complete, took %lld usecs\n", micros());
+
+// TRACE_OTG("transfer complete, result %d\n", result);
+ return result;
+}
+
+
+void usbh_work(void)
+{
+ switch (usbh.state) {
+ case USBH_DISABLED:
+ break;
+ case USBH_INIT:
+ Usb_unfreeze_clock();
+
+ Usb_force_host_mode();
+ //Wr_bitfield(UOTGHS->UOTGHS_HSTCTRL, UOTGHS_HSTCTRL_SPDCONF_Msk, 1, UOTGHS_HSTCTRL_SPDCONF_Pos); // force full/low speed
+ Usb_disable_id_pin();
+
+ Disable_global_interrupt();
+ Usb_disable();
+ (void)Is_usb_enabled();
+ Enable_global_interrupt();
+ Usb_enable_otg_pad();
+ Usb_set_vbof_active_high();
+ Usb_enable_vbus_hw_control();
+ Usb_enable();
+ Host_enable_device_disconnection_interrupt();
+#if !USE_HIGH_SPEED
+ Wr_bitfield(UOTGHS->UOTGHS_HSTCTRL, UOTGHS_HSTCTRL_SPDCONF_Msk, 3, UOTGHS_HSTCTRL_SPDCONF_Pos); // force full speed mode
+#endif
+ usbh.state = USBH_DEVICE_UNATTACHED;
+
+ // clear all ints
+ UOTGHS->UOTGHS_SCR = 0xf;
+ UOTGHS->UOTGHS_HSTICR = 0xf;
+ break;
+
+ case USBH_DEVICE_UNATTACHED:
+ TRACE_OTG("DEVICE_UNATTACHED\n");
+ // clear all ints
+ UOTGHS->UOTGHS_SCR = 0xf;
+ UOTGHS->UOTGHS_HSTICR = 0xf;
+ Host_disable_sof();
+ Host_ack_sof();
+ Usb_enable_vbus();
+
+ // clear all the pipes
+ host_disable_all_pipes();
+ usbh_clear_all_pipes();
+
+ // wipe any accessory state
+ accessory_deinit();
+
+ usbh.state = USBH_WAIT_FOR_DEVICE;
+ TRACE_OTG("USBH_WAIT_FOR_DEVICE\n");
+ break;
+ case USBH_WAIT_FOR_DEVICE:
+ Usb_enable_vbus();
+ if (Is_usb_vbus_high()) {
+ if (Is_host_device_connection()) {
+ TRACE_OTG("vbus high and saw device connection, moving to USBH_DEVICE_ATTACHED\n");
+ Usb_ack_bconnection_error_interrupt();
+ Usb_ack_vbus_error_interrupt();
+ Host_ack_device_connection();
+ Host_ack_hwup();
+ usbh.state = USBH_DEVICE_ATTACHED;
+ }
+ }
+ break;
+ case USBH_DEVICE_ATTACHED:
+ TRACE_OTG("USBH_DEVICE_ATTACHED\n");
+ TRACE_OTG("starting SOF\n");
+ Host_enable_sof();
+
+ // wait 100ms
+ usbh.state = USBH_DEVICE_ATTACHED_SOF_WAIT;
+ usbh_start_sleep();
+ break;
+ case USBH_DEVICE_ATTACHED_SOF_WAIT:
+ if (usbh_sleep_expired(100))
+ usbh.state = USBH_DEVICE_ATTACHED_RESET;
+ break;
+ case USBH_DEVICE_ATTACHED_RESET:
+ // reset the bus
+ TRACE_OTG("USBH_DEVICE_RESET\n");
+ TRACE_OTG("resetting bus\n");
+ Host_send_reset();
+ usbh.state = USBH_DEVICE_ATTACHED_RESET_WAIT;
+ break;
+ case USBH_DEVICE_ATTACHED_RESET_WAIT:
+ if (Is_host_reset_sent()) {
+ Host_ack_reset_sent();
+
+ TRACE_OTG("done with reset\n");
+
+ // wait 100ms
+ usbh.state = USBH_DEVICE_ATTACHED_POST_RESET_WAIT;
+ usbh_start_sleep();
+ }
+ break;
+ case USBH_DEVICE_ATTACHED_POST_RESET_WAIT:
+ if (usbh_sleep_expired(100))
+ usbh.state = USBH_DEVICE_ATTACHED_QUERY;
+ break;
+ case USBH_DEVICE_ATTACHED_QUERY: {
+ TRACE_OTG("USBH_DEVICE_ATTACHED_QUERY\n");
+ TRACE_OTG("configuring ep0 pipe\n");
+ int pip = usbh_setup_endpoint(0, 0, ENDP_TYPE_CONTROL, 8);
+ ASSERT(pip == P_CONTROL);
+
+ // build get device descriptor setup packet, send short version first (to find maxpacketsize)
+ struct usb_setup_packet setup = {
+ USB_SETUP_DIR_DEVICE_TO_HOST,
+ SETUP_GET_DESCRIPTOR,
+ DESCRIPTOR_DEVICE << 8,
+ 0,
+ 8
+ };
+
+ uint8_t buf[64];
+ int len = usbh_send_setup(&setup, buf, true);
+ TRACE_OTG("usbh_send_setup returns %d\n", len);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ // configure the pipe for the maxpacket size the device requests
+ uint32_t maxpacketsize = buf[OFFSET_FIELD_MAXPACKETSIZE];
+ TRACE_OTG("got initial descriptor: len %d, maxpacketsize %d\n", buf[OFFSET_DESCRIPTOR_LENGTH], buf[OFFSET_FIELD_MAXPACKETSIZE]);
+
+ Host_configure_pipe(P_CONTROL, 0, EP_CONTROL, TYPE_CONTROL, TOKEN_SETUP, maxpacketsize, SINGLE_BANK);
+
+ // assign the device an address
+ usbh.dev.address = usbh.next_address++;
+ setup = (struct usb_setup_packet){
+ USB_SETUP_DIR_HOST_TO_DEVICE,
+ SETUP_SET_ADDRESS,
+ usbh.dev.address,
+ 0,
+ 0
+ };
+
+ len = usbh_send_setup(&setup, buf, false);
+ TRACE_OTG("usbh_send_setup returns %d\n", len);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ Host_configure_address(P_CONTROL, usbh.dev.address);
+
+ TRACE_OTG("USBH_DEVICE_ADDRESSED, address %d\n", usbh.dev.address);
+
+ // read the final copy of the device descriptor
+ setup = (struct usb_setup_packet){
+ USB_SETUP_DIR_DEVICE_TO_HOST,
+ SETUP_GET_DESCRIPTOR,
+ DESCRIPTOR_DEVICE << 8,
+ 0,
+ 0x40
+ };
+
+ usbh.dev.devdesc;
+ len = usbh_send_setup(&setup, usbh.dev.devdesc, false);
+ TRACE_OTG("usbh_send_setup returns %d\n", len);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ // pick out the vid/pid
+ usbh.dev.vid = *(uint16_t *)(usbh.dev.devdesc + OFFSET_FIELD_VID);
+ usbh.dev.pid = *(uint16_t *)(usbh.dev.devdesc + OFFSET_FIELD_PID);
+
+ dbgPrintf("USB: found device vid/pid 0x%x/0x%x\n", usbh.dev.vid, usbh.dev.pid);
+
+ // read a copy of the config descriptor
+ setup = (struct usb_setup_packet){
+ USB_SETUP_DIR_DEVICE_TO_HOST,
+ SETUP_GET_DESCRIPTOR,
+ DESCRIPTOR_CONFIGURATION << 8,
+ 0,
+ 9
+ };
+ len = usbh_send_setup(&setup, buf, false);
+ TRACE_OTG("usbh_send_setup returns %d\n", len);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ uint16_t total_config_length = *(uint16_t *)(buf + OFFSET_FIELD_TOTAL_LENGTH);
+
+ TRACE_OTG("config descriptor length %d\n", total_config_length);
+
+ ASSERT(total_config_length < usbh.dev.devconfig);
+
+ // get the final copy of the config descriptor
+ setup = (struct usb_setup_packet){
+ USB_SETUP_DIR_DEVICE_TO_HOST,
+ SETUP_GET_DESCRIPTOR,
+ DESCRIPTOR_CONFIGURATION << 8,
+ 0,
+ total_config_length
+ };
+
+ len = usbh_send_setup(&setup, usbh.dev.devconfig, false);
+ TRACE_OTG("usbh_send_setup returns %d\n", len);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ // see if we're connected to an accessory
+ switch ((usbh.dev.vid << 16) | usbh.dev.pid) {
+ case 0x18d12d00: // accessory
+ case 0x18d12d01: // accessory + adb
+ case 0x18d12d02: // audio
+ case 0x18d12d03: // audio + adb
+ case 0x18d12d04: // accessory + audio
+ case 0x18d12d05: // accessory + audio + adb
+ dbgPrintf("USB: found accessory 0x%x:0x%x\n", usbh.dev.vid, usbh.dev.pid);
+ usbh.state = USBH_DEVICE_ACCESSORY_INIT;
+ break;
+ default:
+ dbgPrintf("USB: didn't find accessory, trying to change its mode\n");
+ usbh.state = USBH_DEVICE_TRY_ACCESSORY;
+ break;
+ }
+
+ break;
+ }
+ case USBH_DEVICE_TRY_ACCESSORY: {
+ // read protocol version
+ struct usb_setup_packet setup = (struct usb_setup_packet){
+ USB_SETUP_DIR_DEVICE_TO_HOST | USB_SETUP_TYPE_VENDOR,
+ 0x33,
+ 0,
+ 0,
+ 2
+ };
+
+ uint16_t version;
+ int len = usbh_send_setup(&setup, &version, false);
+ TRACE_OTG("version len %d\n", len);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ if (len <= 0) {
+ dbgPrintf("USB: no version returned, not accessory\n");
+ usbh.state = USBH_DEVICE_IDLE;
+ break;
+ }
+ TRACE_OTG("accessory returned version %d\n", version);
+ if (version < 1 || version > 2) {
+ dbgPrintf("USB: bad protocol version %d, not accessory\n", version);
+ usbh.state = USBH_DEVICE_IDLE;
+ break;
+ }
+
+ // send google strings
+ setup = (struct usb_setup_packet){
+ USB_SETUP_DIR_HOST_TO_DEVICE | USB_SETUP_TYPE_VENDOR,
+ 0x34,
+ 0,
+ 0,
+ 0
+ };
+
+ setup.wIndex = 0;
+ setup.wLength = strlen(usbh.accessory_string_vendor) + 1;
+ len = usbh_send_setup(&setup, (void *)usbh.accessory_string_vendor, false);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ setup.wIndex = 1;
+ setup.wLength = strlen(usbh.accessory_string_name) + 1;
+ len = usbh_send_setup(&setup, (void *)usbh.accessory_string_name, false);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ setup.wIndex = 2;
+ setup.wLength = strlen(usbh.accessory_string_longname) + 1;
+ len = usbh_send_setup(&setup, (void *)usbh.accessory_string_longname, false);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ setup.wIndex = 3;
+ setup.wLength = strlen(usbh.accessory_string_version) + 1;
+ len = usbh_send_setup(&setup, (void *)usbh.accessory_string_version, false);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ setup.wIndex = 4;
+ setup.wLength = strlen(usbh.accessory_string_url) + 1;
+ len = usbh_send_setup(&setup, (void *)usbh.accessory_string_url, false);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ setup.wIndex = 5;
+ setup.wLength = strlen(usbh.accessory_string_serial) + 1;
+ len = usbh_send_setup(&setup, (void *)usbh.accessory_string_serial, false);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+#if AUDIO_ACCESSORY
+ // we want audio
+ setup = (struct usb_setup_packet){
+ USB_SETUP_DIR_HOST_TO_DEVICE | USB_SETUP_TYPE_VENDOR,
+ 0x3a,
+ 1,
+ 0,
+ 0
+ };
+ len = usbh_send_setup(&setup, NULL, false);
+ if (len == PIPE_RESULT_RESET)
+ break;
+#endif
+
+ // send accessory start
+ setup = (struct usb_setup_packet){
+ USB_SETUP_DIR_HOST_TO_DEVICE | USB_SETUP_TYPE_VENDOR,
+ 0x35,
+ 0,
+ 0,
+ 0
+ };
+ len = usbh_send_setup(&setup, NULL, false);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ // go to idle mode, wait for the device to reset out from underneath us
+ usbh.state = USBH_DEVICE_IDLE;
+ break;
+ }
+ case USBH_DEVICE_ACCESSORY_INIT: {
+ // set configuration 1
+ struct usb_setup_packet setup = (struct usb_setup_packet){
+ USB_SETUP_DIR_HOST_TO_DEVICE,
+ SETUP_SET_CONFIGURATION,
+ 1,
+ 0,
+ 0
+ };
+
+ int len = usbh_send_setup(&setup, NULL, false);
+ if (len == PIPE_RESULT_RESET)
+ break;
+
+ int ret = accessory_init(&usbh.dev);
+ if (ret < 0) {
+ usbh.state = USBH_DEVICE_IDLE;
+ break;
+ }
+
+ usbh.state = USBH_DEVICE_ACCESSORY;
+ break;
+ }
+ case USBH_DEVICE_ACCESSORY:
+ case USBH_DEVICE_IDLE:
+ //TRACE_OTG("USBH_DEVICE_WAIT\n");
+ break;
+ }
+}
+
+/* for the coop threading system */
+static void usbhTask(void* ptr)
+{
+ for (;;) {
+ coopYield();
+ usbh_work();
+ // let the accessory state machine run
+ accessory_work();
+ }
+}
+
+void usbh_init(void)
+{
+ usbh.next_address = 1;
+ usbh.state = USBH_DISABLED;
+
+ /* default accessory strings */
+ usbh.accessory_string_vendor = DEFAULT_ACCESSORY_STRING_VENDOR;
+ usbh.accessory_string_name = DEFAULT_ACCESSORY_STRING_NAME;
+ usbh.accessory_string_longname = DEFAULT_ACCESSORY_STRING_LONGNAME;
+ usbh.accessory_string_version = DEFAULT_ACCESSORY_STRING_VERSION;
+ usbh.accessory_string_url = DEFAULT_ACCESSORY_STRING_URL;
+ usbh.accessory_string_serial = DEFAULT_ACCESSORY_STRING_SERIAL;
+
+ /* Enable peripheral clock for UOTGHS */
+ pmc_enable_periph_clk(ID_UOTGHS);
+
+#if 1
+ /* Enable UPLL 480 MHz */
+ PMC->CKGR_UCKR = CKGR_UCKR_UPLLEN | CKGR_UCKR_UPLLCOUNT(7);
+ /* Wait that UPLL is considered locked by the PMC */
+ while( !(PMC->PMC_SR & PMC_SR_LOCKU) )
+ ;
+
+ /* USB clock register: USB Clock Input is UTMI PLL */
+ PMC->PMC_USB = PMC_USB_USBS | PMC_USB_USBDIV(0);
+
+ PMC->PMC_SCER = PMC_SCER_UOTGCLK;
+#else
+ PMC->CKGR_UCKR = 0;
+ PMC->PMC_USB = PMC_USB_USBDIV(0);
+ PMC->PMC_SCER = PMC_SCER_UOTGCK;
+#endif
+
+ NVIC_SetPriority(UOTGHS_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
+ NVIC_EnableIRQ(UOTGHS_IRQn);
+
+ coopSpawn(&usbhTask, NULL, 2048);
+
+ Usb_freeze_clock();
+}
+
+void usbh_set_accessory_string_vendor(const char *str)
+{
+ usbh.accessory_string_vendor = str;
+}
+
+void usbh_set_accessory_string_name(const char *str)
+{
+ usbh.accessory_string_name = str;
+}
+
+void usbh_set_accessory_string_longname(const char *str)
+{
+ usbh.accessory_string_longname = str;
+}
+
+void usbh_set_accessory_string_version(const char *str)
+{
+ usbh.accessory_string_version = str;
+}
+
+void usbh_set_accessory_string_url(const char *str)
+{
+ usbh.accessory_string_url = str;
+}
+
+void usbh_set_accessory_string_serial(const char *str)
+{
+ usbh.accessory_string_serial = str;
+}
+
+void usbh_start(void)
+{
+ if (usbh.state == USBH_DISABLED)
+ usbh.state = USBH_INIT;
+}
+
+int usbh_accessory_connected(void)
+{
+ return usbh.state == USBH_DEVICE_ACCESSORY;
+}
+