// ======================================================================
// 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
        }
}