// ======================================================================
// USB driver
//
// Entry points:
// usb_init() - enable the USB interrupt
// usb_poll() - poll for incoming packets and process them
//
// This code communicates with the interrupt handler through a number of
// global variables, including two input buffers and one output buffer.
// Packets are queued for transmission by copying them into the output
// buffer. The interrupt handler will transmit such a packet on the
// reception of an IN packet.
//
// Standard SETUP packets are handled here. Non-standard SETUP packets
// are forwarded to the application code by calling usb_setup(). The
// macros USBTINY_CALLBACK_IN and USBTINY_CALLBACK_OUT control whether
// the callback functions usb_in() and usb_out() will be called for IN
// and OUT transfers.
//
// Maximum stack usage (gcc 4.1.0 & 4.3.4) of usb_poll(): 5 bytes plus
// possible additional stack usage in usb_setup(), usb_in() or usb_out().
//
// Copyright 2006-2010 Dick Streefland
//
// This is free software, licensed under the terms of the GNU General
// Public License as published by the Free Software Foundation.
// ======================================================================
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include "def.h"
#include "usb.h"
#define LE(word) (word) & 0xff, (word) >> 8
// ----------------------------------------------------------------------
// USB constants
// ----------------------------------------------------------------------
enum
{
DESCRIPTOR_TYPE_DEVICE = 1,
DESCRIPTOR_TYPE_CONFIGURATION,
DESCRIPTOR_TYPE_STRING,
DESCRIPTOR_TYPE_INTERFACE,
DESCRIPTOR_TYPE_ENDPOINT,
};
// ----------------------------------------------------------------------
// Interrupt handler interface
// ----------------------------------------------------------------------
byte_t usb_rx_buf[2*USB_BUFSIZE]; // two input buffers
byte_t usb_rx_off; // buffer offset: 0 or USB_BUFSIZE
byte_t usb_rx_len; // buffer size, 0 means empty
byte_t usb_rx_token; // PID of token packet: SETUP or OUT
byte_t usb_tx_buf[USB_BUFSIZE]; // output buffer
byte_t usb_tx_len; // output buffer size, 0 means empty
byte_t usb_address; // assigned device address
byte_t usb_new_address; // new device address
// ----------------------------------------------------------------------
// Local data
// ----------------------------------------------------------------------
enum
{
TX_STATE_IDLE = 0, // transmitter idle
TX_STATE_RAM, // usb_tx_data is a RAM address
TX_STATE_ROM, // usb_tx_data is a ROM address
TX_STATE_CALLBACK, // call usb_in() to obtain transmit data
};
static byte_t usb_tx_state; // TX_STATE_*, see enum above
static byte_t usb_tx_total; // total transmit size
static byte_t* usb_tx_data; // pointer to data to transmit
#if defined USBTINY_VENDOR_NAME
struct
{
byte_t length;
byte_t type;
int string[sizeof(USBTINY_VENDOR_NAME)-1];
} const string_vendor PROGMEM =
{
2 * sizeof(USBTINY_VENDOR_NAME),
DESCRIPTOR_TYPE_STRING,
{ CAT2(L, USBTINY_VENDOR_NAME) }
};
# define VENDOR_NAME_ID 1
#else
# define VENDOR_NAME_ID 0
#endif
#if defined USBTINY_DEVICE_NAME
struct
{
byte_t length;
byte_t type;
int string[sizeof(USBTINY_DEVICE_NAME)-1];
} const string_device PROGMEM =
{
2 * sizeof(USBTINY_DEVICE_NAME),
DESCRIPTOR_TYPE_STRING,
{ CAT2(L, USBTINY_DEVICE_NAME) }
};
# define DEVICE_NAME_ID 2
#else
# define DEVICE_NAME_ID 0
#endif
#if defined USBTINY_SERIAL
struct
{
byte_t length;
byte_t type;
int string[sizeof(USBTINY_SERIAL)-1];
} const string_serial PROGMEM =
{
2 * sizeof(USBTINY_SERIAL),
DESCRIPTOR_TYPE_STRING,
{ CAT2(L, USBTINY_SERIAL) }
};
# define SERIAL_ID 3
#else
# define SERIAL_ID 0
#endif
#if VENDOR_NAME_ID || DEVICE_NAME_ID || SERIAL_ID
static byte_t const string_langid [] PROGMEM =
{
4, // bLength
DESCRIPTOR_TYPE_STRING, // bDescriptorType (string)
LE(0x0409), // wLANGID[0] (American English)
};
#endif
// Device Descriptor
static byte_t const descr_device [18] PROGMEM =
{
18, // bLength
DESCRIPTOR_TYPE_DEVICE, // bDescriptorType
LE(0x0101), // bcdUSB
USBTINY_DEVICE_CLASS, // bDeviceClass
USBTINY_DEVICE_SUBCLASS, // bDeviceSubClass
USBTINY_DEVICE_PROTOCOL, // bDeviceProtocol
8, // bMaxPacketSize0
LE(USBTINY_VENDOR_ID), // idVendor
LE(USBTINY_DEVICE_ID), // idProduct
LE(USBTINY_DEVICE_VERSION), // bcdDevice
VENDOR_NAME_ID, // iManufacturer
DEVICE_NAME_ID, // iProduct
SERIAL_ID, // iSerialNumber
1, // bNumConfigurations
};
// Configuration Descriptor
static byte_t const descr_config [] PROGMEM =
{
9, // bLength
DESCRIPTOR_TYPE_CONFIGURATION, // bDescriptorType
LE(9+9+7*USBTINY_ENDPOINT), // wTotalLength
1, // bNumInterfaces
1, // bConfigurationValue
0, // iConfiguration
(USBTINY_MAX_POWER ? 0x80 : 0xc0), // bmAttributes
(USBTINY_MAX_POWER + 1) / 2, // MaxPower
// Standard Interface Descriptor
9, // bLength
DESCRIPTOR_TYPE_INTERFACE, // bDescriptorType
0, // bInterfaceNumber
0, // bAlternateSetting
USBTINY_ENDPOINT, // bNumEndpoints
USBTINY_INTERFACE_CLASS, // bInterfaceClass
USBTINY_INTERFACE_SUBCLASS, // bInterfaceSubClass
USBTINY_INTERFACE_PROTOCOL, // bInterfaceProtocol
0, // iInterface
#if USBTINY_ENDPOINT
// Additional Endpoint
7, // bLength
DESCRIPTOR_TYPE_ENDPOINT, // bDescriptorType
USBTINY_ENDPOINT_ADDRESS, // bEndpointAddress
USBTINY_ENDPOINT_TYPE, // bmAttributes
LE(8), // wMaxPacketSize
USBTINY_ENDPOINT_INTERVAL, // bInterval
#endif
};
// ----------------------------------------------------------------------
// Inspect an incoming packet.
// ----------------------------------------------------------------------
static void usb_receive ( byte_t* data, byte_t rx_len )
{
byte_t len;
byte_t type;
byte_t limit;
usb_tx_state = TX_STATE_RAM;
len = 0;
if ( usb_rx_token == USB_PID_SETUP )
{
limit = data[6];
if ( data[7] )
{
limit = 255;
}
type = data[0] & 0x60;
if ( type == 0x00 )
{ // Standard request
if ( data[1] == 0 ) // GET_STATUS
{
len = 2;
#if USBTINY_MAX_POWER == 0
data[0] = (data[0] == 0x80);
#else
data[0] = 0;
#endif
data[1] = 0;
}
else if ( data[1] == 5 ) // SET_ADDRESS
{
usb_new_address = data[2];
#ifdef USBTINY_USB_OK_LED
SET(USBTINY_USB_OK_LED);// LED on
#endif
}
else if ( data[1] == 6 ) // GET_DESCRIPTOR
{
usb_tx_state = TX_STATE_ROM;
if ( data[3] == 1 )
{ // DEVICE
data = (byte_t*) &descr_device;
len = sizeof(descr_device);
}
else if ( data[3] == 2 )
{ // CONFIGURATION
data = (byte_t*) &descr_config;
len = sizeof(descr_config);
}
#if VENDOR_NAME_ID || DEVICE_NAME_ID || SERIAL_ID
else if ( data[3] == 3 )
{ // STRING
if ( data[2] == 0 )
{
data = (byte_t*) &string_langid;
len = sizeof(string_langid);
}
#if VENDOR_NAME_ID
else if ( data[2] == VENDOR_NAME_ID )
{
data = (byte_t*) &string_vendor;
len = sizeof(string_vendor);
}
#endif
#if DEVICE_NAME_ID
else if ( data[2] == DEVICE_NAME_ID )
{
data = (byte_t*) &string_device;
len = sizeof(string_device);
}
#endif
#if SERIAL_ID
else if ( data[2] == SERIAL_ID )
{
data = (byte_t*) &string_serial;
len = sizeof(string_serial);
}
#endif
}
#endif
}
else if ( data[1] == 8 ) // GET_CONFIGURATION
{
data[0] = 1; // return bConfigurationValue
len = 1;
}
else if ( data[1] == 10 ) // GET_INTERFACE
{
data[0] = 0;
len = 1;
}
}
else
{ // Class or Vendor request
len = usb_setup( data );
#if USBTINY_CALLBACK_IN
if ( len == 0xff )
{
usb_tx_state = TX_STATE_CALLBACK;
}
#endif
}
if ( len > limit )
{
len = limit;
}
usb_tx_data = data;
}
#if USBTINY_CALLBACK_OUT
else if ( rx_len > 0 )
{ // usb_rx_token == USB_PID_OUT
usb_out( data, rx_len );
}
#endif
usb_tx_total = len;
usb_tx_buf[0] = USB_PID_DATA0; // next data packet will be DATA1
}
// ----------------------------------------------------------------------
// Load the transmit buffer with the next packet.
// ----------------------------------------------------------------------
static void usb_transmit ( void )
{
byte_t len;
byte_t* src;
byte_t* dst;
byte_t i;
byte_t b;
usb_tx_buf[0] ^= (USB_PID_DATA0 ^ USB_PID_DATA1);
len = usb_tx_total;
if ( len > 8 )
{
len = 8;
}
dst = usb_tx_buf + 1;
if ( len > 0 )
{
#if USBTINY_CALLBACK_IN
if ( usb_tx_state == TX_STATE_CALLBACK )
{
len = usb_in( dst, len );
}
else
#endif
{
src = usb_tx_data;
if ( usb_tx_state == TX_STATE_RAM )
{
for ( i = 0; i < len; i++ )
{
*dst++ = *src++;
}
}
else // usb_tx_state == TX_STATE_ROM
{
for ( i = 0; i < len; i++ )
{
b = pgm_read_byte( src );
src++;
*dst++ = b;
}
}
usb_tx_data = src;
}
usb_tx_total -= len;
}
crc( usb_tx_buf + 1, len );
usb_tx_len = len + 3;
if ( len < 8 )
{ // this is the last packet
usb_tx_state = TX_STATE_IDLE;
}
}
// ----------------------------------------------------------------------
// Initialize the low-level USB driver.
// ----------------------------------------------------------------------
extern void usb_init ( void )
{
USB_INT_CONFIG |= USB_INT_CONFIG_SET;
USB_INT_ENABLE |= (1 << USB_INT_ENABLE_BIT);
#ifdef USBTINY_USB_OK_LED
OUTPUT(USBTINY_USB_OK_LED);
#endif
#ifdef USBTINY_DMINUS_PULLUP
SET(USBTINY_DMINUS_PULLUP);
OUTPUT(USBTINY_DMINUS_PULLUP); // enable pullup on D-
#endif
sei();
}
// ----------------------------------------------------------------------
// Poll USB driver:
// - check for incoming USB packets
// - refill an empty transmit buffer
// - check for USB bus reset
// ----------------------------------------------------------------------
extern void usb_poll ( void )
{
byte_t i;
// check for incoming USB packets
if ( usb_rx_len != 0 )
{
usb_receive( usb_rx_buf + USB_BUFSIZE - usb_rx_off + 1, usb_rx_len - 3 );
usb_tx_len = 0; // abort pending transmission
usb_rx_len = 0; // accept next packet
}
// refill an empty transmit buffer, when the transmitter is active
if ( usb_tx_len == 0 && usb_tx_state != TX_STATE_IDLE )
{
usb_transmit();
}
// check for USB bus reset
for ( i = 10; i > 0 && ! (USB_IN & USB_MASK_DMINUS); i-- )
{
}
if ( i == 0 )
{ // SE0 for more than 2.5uS is a reset
usb_new_address = 0;
usb_address = 0;
#ifdef USBTINY_USB_OK_LED
CLR(USBTINY_USB_OK_LED); // LED off
#endif
}
}