|
|
@@ -0,0 +1,453 @@ |
|
|
|
/* USB EHCI Host for Teensy 3.6 |
|
|
|
* Copyright 2017 Paul Stoffregen (paul@pjrc.com) |
|
|
|
* |
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a |
|
|
|
* copy of this software and associated documentation files (the |
|
|
|
* "Software"), to deal in the Software without restriction, including |
|
|
|
* without limitation the rights to use, copy, modify, merge, publish, |
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to |
|
|
|
* permit persons to whom the Software is furnished to do so, subject to |
|
|
|
* the following conditions: |
|
|
|
* |
|
|
|
* The above copyright notice and this permission notice shall be included |
|
|
|
* in all copies or substantial portions of the Software. |
|
|
|
* |
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
|
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
|
|
* |
|
|
|
*/ |
|
|
|
|
|
|
|
#include <Arduino.h> |
|
|
|
#include "USBHost_t36.h" // Read this header first for key info |
|
|
|
|
|
|
|
#define print USBHost::print_ |
|
|
|
#define println USBHost::println_ |
|
|
|
|
|
|
|
#define ADK_VID 0x18D1 |
|
|
|
#define ADK_PID 0x2D00 |
|
|
|
#define ADB_PID 0x2D01 |
|
|
|
#define USB_SETUP_DEVICE_TO_HOST 0x80 |
|
|
|
#define USB_SETUP_HOST_TO_DEVICE 0x00 |
|
|
|
#define USB_SETUP_TYPE_VENDOR 0x40 |
|
|
|
#define USB_SETUP_RECIPIENT_DEVICE 0x00 |
|
|
|
#define UHS_ADK_bmREQ_GET USB_SETUP_DEVICE_TO_HOST|USB_SETUP_TYPE_VENDOR|USB_SETUP_RECIPIENT_DEVICE |
|
|
|
#define UHS_ADK_bmREQ_SEND USB_SETUP_HOST_TO_DEVICE|USB_SETUP_TYPE_VENDOR|USB_SETUP_RECIPIENT_DEVICE |
|
|
|
#define UHS_ADK_GETPROTO 51 // check USB accessory protocol version |
|
|
|
#define UHS_ADK_SENDSTR 52 // send identifying string |
|
|
|
#define UHS_ADK_ACCSTART 53 // start device in accessory mode |
|
|
|
#define UHS_ADK_ID_MANUFACTURER 0 |
|
|
|
#define UHS_ADK_ID_MODEL 1 |
|
|
|
#define UHS_ADK_ID_DESCRIPTION 2 |
|
|
|
#define UHS_ADK_ID_VERSION 3 |
|
|
|
#define UHS_ADK_ID_URI 4 |
|
|
|
#define UHS_ADK_ID_SERIAL 5 |
|
|
|
|
|
|
|
static uint8_t adkbuf[256] __attribute__ ((aligned(16))); |
|
|
|
static setup_t adksetup __attribute__ ((aligned(16))); |
|
|
|
|
|
|
|
/************************************************************/ |
|
|
|
// Initialization and claiming of devices & interfaces |
|
|
|
/************************************************************/ |
|
|
|
|
|
|
|
void ADK::init() |
|
|
|
{ |
|
|
|
contribute_Pipes(mypipes, sizeof(mypipes)/sizeof(Pipe_t)); |
|
|
|
contribute_Transfers(mytransfers, sizeof(mytransfers)/sizeof(Transfer_t)); |
|
|
|
|
|
|
|
rx_head = 0; |
|
|
|
rx_tail = 0; |
|
|
|
driver_ready_for_device(this); |
|
|
|
|
|
|
|
state = 0; |
|
|
|
} |
|
|
|
|
|
|
|
bool ADK::claim(Device_t *dev, int type, const uint8_t *descriptors, uint32_t len) |
|
|
|
{ |
|
|
|
// only claim at interface level |
|
|
|
if (type != 1) |
|
|
|
return false; |
|
|
|
|
|
|
|
// Check for ADK or ADB PIDs |
|
|
|
if (dev->idVendor == ADK_VID && (dev->idProduct == ADK_PID || dev->idProduct == ADB_PID)) |
|
|
|
{ |
|
|
|
const uint8_t *p = descriptors; |
|
|
|
const uint8_t *end = p + len; |
|
|
|
|
|
|
|
// Interface descriptor |
|
|
|
if (p[0] != 9 || p[1] != 4) |
|
|
|
return false; |
|
|
|
|
|
|
|
// bInterfaceClass: 255 Vendor Specific |
|
|
|
if (p[5] != 255) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
// bInterfaceSubClass: 255 |
|
|
|
if (p[6] != 255) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
println("ADK claim this=", (uint32_t)this, HEX); |
|
|
|
print("vid=", dev->idVendor, HEX); |
|
|
|
print(", pid=", dev->idProduct, HEX); |
|
|
|
print(", bDeviceClass = ", dev->bDeviceClass); |
|
|
|
print(", bDeviceSubClass = ", dev->bDeviceSubClass); |
|
|
|
println(", bDeviceProtocol = ", dev->bDeviceProtocol); |
|
|
|
println(" bInterfaceClass=", p[5]); |
|
|
|
println(" bInterfaceSubClass=", p[6]); |
|
|
|
print_hexbytes(descriptors, len); |
|
|
|
|
|
|
|
p += 9; |
|
|
|
rx_ep = 0; |
|
|
|
tx_ep = 0; |
|
|
|
|
|
|
|
while (p < end) |
|
|
|
{ |
|
|
|
len = *p; |
|
|
|
|
|
|
|
if (len < 4) |
|
|
|
return false; // all desc are at least 4 bytes |
|
|
|
|
|
|
|
if (p + len > end) |
|
|
|
return false; // reject if beyond end of data |
|
|
|
|
|
|
|
uint32_t type = p[1]; |
|
|
|
if (type == 5) |
|
|
|
{ |
|
|
|
// endpoint descriptor |
|
|
|
if (p[0] < 7) |
|
|
|
return false; // at least 7 bytes |
|
|
|
|
|
|
|
if (p[3] != 2) |
|
|
|
return false; // must be bulk type |
|
|
|
|
|
|
|
println(" Endpoint: ", p[2], HEX); |
|
|
|
switch (p[2] & 0xF0) |
|
|
|
{ |
|
|
|
case 0x80: |
|
|
|
// IN endpoint |
|
|
|
if (rx_ep == 0) |
|
|
|
{ |
|
|
|
rx_ep = p[2] & 0x0F; |
|
|
|
rx_size = p[4] | (p[5] << 8); |
|
|
|
println(" rx_size = ", rx_size); |
|
|
|
println(" rx_ep = ", rx_ep); |
|
|
|
} |
|
|
|
break; |
|
|
|
case 0x00: |
|
|
|
// OUT endpoint |
|
|
|
if (tx_ep == 0) |
|
|
|
{ |
|
|
|
tx_ep = p[2]; |
|
|
|
tx_size = p[4] | (p[5] << 8); |
|
|
|
println(" tx_size = ", tx_size); |
|
|
|
println(" tx_ep = ", tx_ep); |
|
|
|
} |
|
|
|
break; |
|
|
|
default: |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// ADK uses the first two endpoints for communication |
|
|
|
if (rx_ep && tx_ep) |
|
|
|
{ |
|
|
|
println("Found both rx and tx EPs"); |
|
|
|
break; |
|
|
|
} |
|
|
|
p += len; |
|
|
|
} |
|
|
|
|
|
|
|
// if an IN endpoint was found, create its pipe |
|
|
|
if (rx_ep && rx_size <= MAX_PACKET_SIZE) |
|
|
|
{ |
|
|
|
rxpipe = new_Pipe(dev, 2, rx_ep, 1, rx_size); |
|
|
|
if (rxpipe) |
|
|
|
{ |
|
|
|
rxpipe->callback_function = rx_callback; |
|
|
|
queue_Data_Transfer(rxpipe, rx_buffer, rx_size, this); |
|
|
|
rx_packet_queued = true; |
|
|
|
println("Done creating RX pipe"); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
rxpipe = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
// if an OUT endpoint was found, create its pipe |
|
|
|
if (tx_ep && tx_size <= MAX_PACKET_SIZE) |
|
|
|
{ |
|
|
|
txpipe = new_Pipe(dev, 2, tx_ep, 0, tx_size); |
|
|
|
if (txpipe) |
|
|
|
{ |
|
|
|
txpipe->callback_function = tx_callback; |
|
|
|
println("Done creating TX pipe"); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
txpipe = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
rx_head = 0; |
|
|
|
rx_tail = 0; |
|
|
|
|
|
|
|
// claim if either pipe created |
|
|
|
bool created = (rxpipe || txpipe); |
|
|
|
|
|
|
|
if (created) |
|
|
|
{ |
|
|
|
println("Done with init."); |
|
|
|
state = 8; |
|
|
|
} |
|
|
|
return created; |
|
|
|
|
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
state = 0; |
|
|
|
println("Not in accessory mode."); |
|
|
|
|
|
|
|
// Kick off switch to Accessory Mode |
|
|
|
mk_setup(adksetup, UHS_ADK_bmREQ_GET, UHS_ADK_GETPROTO, 0, 0, 2); |
|
|
|
queue_Control_Transfer(dev, &adksetup, adkbuf, this); |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
void ADK::sendStr(Device_t *dev, uint8_t index, char *str) |
|
|
|
{ |
|
|
|
strcpy(adkbuf, (unsigned char*)str); |
|
|
|
mk_setup(adksetup, UHS_ADK_bmREQ_SEND, UHS_ADK_SENDSTR, 0, index, strlen(str)); |
|
|
|
queue_Control_Transfer(dev, &adksetup, adkbuf, this); |
|
|
|
} |
|
|
|
|
|
|
|
void ADK::control(const Transfer_t *transfer) |
|
|
|
{ |
|
|
|
println("Control callback state=%i",state); |
|
|
|
|
|
|
|
switch (state) |
|
|
|
{ |
|
|
|
case 0: |
|
|
|
print_hexbytes(transfer->buffer, transfer->length); |
|
|
|
state++; |
|
|
|
sendStr(device, UHS_ADK_ID_MANUFACTURER, manufacturer); |
|
|
|
break; |
|
|
|
case 1: |
|
|
|
print_hexbytes(transfer->buffer, transfer->length); |
|
|
|
state++; |
|
|
|
sendStr(device, UHS_ADK_ID_MODEL, model); |
|
|
|
break; |
|
|
|
case 2: |
|
|
|
print_hexbytes(transfer->buffer, transfer->length); |
|
|
|
state++; |
|
|
|
sendStr(device, UHS_ADK_ID_DESCRIPTION, desc); |
|
|
|
break; |
|
|
|
case 3: |
|
|
|
print_hexbytes(transfer->buffer, transfer->length); |
|
|
|
state++; |
|
|
|
sendStr(device, UHS_ADK_ID_VERSION, version); |
|
|
|
break; |
|
|
|
case 4: |
|
|
|
print_hexbytes(transfer->buffer, transfer->length); |
|
|
|
state++; |
|
|
|
sendStr(device, UHS_ADK_ID_URI, uri); |
|
|
|
break; |
|
|
|
case 5: |
|
|
|
print_hexbytes(transfer->buffer, transfer->length); |
|
|
|
state++; |
|
|
|
sendStr(device, UHS_ADK_ID_SERIAL, serial); |
|
|
|
break; |
|
|
|
case 6: |
|
|
|
print_hexbytes(transfer->buffer, transfer->length); |
|
|
|
state++; |
|
|
|
|
|
|
|
// Send ADK switch command |
|
|
|
mk_setup(adksetup, UHS_ADK_bmREQ_SEND, UHS_ADK_ACCSTART, 0, 0, 0); |
|
|
|
queue_Control_Transfer(device, &adksetup, NULL, this); |
|
|
|
break; |
|
|
|
case 7: |
|
|
|
println("After ACC switch command. Device should re-enumerate."); |
|
|
|
print_hexbytes(transfer->buffer, transfer->length); |
|
|
|
state++; |
|
|
|
break; |
|
|
|
default: |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void ADK::rx_callback(const Transfer_t *transfer) |
|
|
|
{ |
|
|
|
if (transfer->driver) { |
|
|
|
((ADK *)(transfer->driver))->rx_data(transfer); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void ADK::tx_callback(const Transfer_t *transfer) |
|
|
|
{ |
|
|
|
if (transfer->driver) { |
|
|
|
((ADK *)(transfer->driver))->tx_data(transfer); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void ADK::rx_data(const Transfer_t *transfer) |
|
|
|
{ |
|
|
|
uint32_t len = transfer->length - ((transfer->qtd.token >> 16) & 0x7FFF); |
|
|
|
|
|
|
|
println("ADK Receive rx_data"); |
|
|
|
print("Len: "); |
|
|
|
print(len); |
|
|
|
print(" Data: "); |
|
|
|
print_hexbytes(transfer->buffer, len); |
|
|
|
|
|
|
|
uint32_t head = rx_head; |
|
|
|
uint32_t tail = rx_tail; |
|
|
|
|
|
|
|
for (uint32_t i=0; i < ceil(len/4); i++) |
|
|
|
{ |
|
|
|
uint32_t msg = rx_buffer[i]; |
|
|
|
if (msg) { |
|
|
|
if (++head >= RX_QUEUE_SIZE) |
|
|
|
head = 0; |
|
|
|
rx_queue[head] = msg; |
|
|
|
} |
|
|
|
} |
|
|
|
rx_head = head; |
|
|
|
|
|
|
|
rx_packet_queued = false; |
|
|
|
rx_queue_packets(head, tail); |
|
|
|
} |
|
|
|
|
|
|
|
void ADK::rx_queue_packets(uint32_t head, uint32_t tail) |
|
|
|
{ |
|
|
|
if (rx_packet_queued) |
|
|
|
return; |
|
|
|
|
|
|
|
uint32_t avail = (head < tail) ? tail - head - 1 : RX_QUEUE_SIZE - 1 - head + tail; |
|
|
|
|
|
|
|
println("rx_size = ", rx_size); |
|
|
|
println("avail = ", avail); |
|
|
|
if (avail >= (uint32_t)(rx_size>>2)) { |
|
|
|
// enough space to accept another full packet |
|
|
|
println("queue another receive packet"); |
|
|
|
queue_Data_Transfer(rxpipe, rx_buffer, rx_size, this); |
|
|
|
rx_packet_queued = true; |
|
|
|
} else { |
|
|
|
// queue can't accept another packet's data, so leave |
|
|
|
// the data waiting on the device until we can accept it |
|
|
|
println("wait to receive more packets"); |
|
|
|
rx_packet_queued = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void ADK::tx_data(const Transfer_t *transfer) |
|
|
|
{ |
|
|
|
println("ADK tx_data transmit complete"); |
|
|
|
print(" Data: "); |
|
|
|
print_hexbytes(transfer->buffer, tx_size); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void ADK::disconnect() |
|
|
|
{ |
|
|
|
println("Disconnect"); |
|
|
|
|
|
|
|
rxpipe = NULL; |
|
|
|
txpipe = NULL; |
|
|
|
|
|
|
|
state = 0; |
|
|
|
} |
|
|
|
|
|
|
|
void ADK::begin(char *adk_manufacturer, char *adk_model, char *adk_desc, char *adk_version, char *adk_uri, char *adk_serial) |
|
|
|
{ |
|
|
|
manufacturer = adk_manufacturer; |
|
|
|
model = adk_model; |
|
|
|
desc = adk_desc; |
|
|
|
version = adk_version; |
|
|
|
uri = adk_uri; |
|
|
|
serial = adk_serial; |
|
|
|
} |
|
|
|
|
|
|
|
void ADK::end(void) |
|
|
|
{ |
|
|
|
// TODO: add end code |
|
|
|
} |
|
|
|
|
|
|
|
int ADK::available(void) |
|
|
|
{ |
|
|
|
if (!device) |
|
|
|
return 0; |
|
|
|
|
|
|
|
uint32_t head = rx_head; |
|
|
|
uint32_t tail = rx_tail; |
|
|
|
|
|
|
|
if (head >= tail) |
|
|
|
return head - tail; |
|
|
|
|
|
|
|
return RX_QUEUE_SIZE + head - tail; |
|
|
|
} |
|
|
|
|
|
|
|
int ADK::peek(void) |
|
|
|
{ |
|
|
|
if (!device) |
|
|
|
return -1; |
|
|
|
|
|
|
|
uint32_t head = rx_head; |
|
|
|
uint32_t tail = rx_tail; |
|
|
|
|
|
|
|
if (head == tail) |
|
|
|
return -1; |
|
|
|
|
|
|
|
if (++tail >= RX_QUEUE_SIZE) |
|
|
|
tail = 0; |
|
|
|
|
|
|
|
return rx_queue[tail]; |
|
|
|
} |
|
|
|
|
|
|
|
int ADK::read(void) |
|
|
|
{ |
|
|
|
if (!device) |
|
|
|
return -1; |
|
|
|
|
|
|
|
uint32_t head = rx_head; |
|
|
|
uint32_t tail = rx_tail; |
|
|
|
|
|
|
|
if (head == tail) |
|
|
|
return -1; |
|
|
|
|
|
|
|
if (++tail >= RX_QUEUE_SIZE) |
|
|
|
tail = 0; |
|
|
|
|
|
|
|
int c = rx_queue[tail]; |
|
|
|
rx_tail = tail; |
|
|
|
|
|
|
|
rx_queue_packets(head, tail); |
|
|
|
|
|
|
|
return c; |
|
|
|
} |
|
|
|
|
|
|
|
size_t ADK::write(size_t len, uint8_t *buf) |
|
|
|
{ |
|
|
|
memcpy(tx_buffer, buf, len); |
|
|
|
__disable_irq(); |
|
|
|
queue_Data_Transfer(txpipe, tx_buffer, len, this); |
|
|
|
__enable_irq(); |
|
|
|
|
|
|
|
return len; |
|
|
|
} |
|
|
|
|
|
|
|
bool ADK::ready() |
|
|
|
{ |
|
|
|
if (state > 7) |
|
|
|
return true; |
|
|
|
return false; |
|
|
|
} |