| /* |
| * 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; |
| } |
| |