blob: 9123ba0653d38e0f11b21606870e1a7c433ef068 [file] [log] [blame]
/*
* 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;
}