// ======================================================================
// 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 3.4.3 & 4.1.0) of usb_poll(): 5 bytes plus
// possible additional stack usage in usb_setup(), usb_in() or usb_out().
//
// Copyright (C) 2006 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 USB 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
static	byte_t	new_address;		// new device address

#if	defined USBTINY_VENDOR_NAME
struct
{
	byte_t	length;
	byte_t	type;
	int	string[sizeof(USBTINY_VENDOR_NAME)-1];
}	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];
}	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];
}	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	string_langid [] PROGMEM =
{
	4,				// bLength
	DESCRIPTOR_TYPE_STRING,		// bDescriptorType (string)
	LE(0x0409),			// wLANGID[0] (American English)
};
#endif

// Device Descriptor
static	byte_t	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	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
			{
				new_address = data[2];
			}
			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 we got this far, there's a good chance everything is OK with enumeration so turn on the OK led
					PORTD |= _BV(5);

				}
#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);
	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 )
	{
		if	( usb_tx_state != TX_STATE_IDLE )
		{
			usb_transmit();
		}
		else
		{	// change the USB address at the end of a transfer
			usb_address = new_address;
		}
	}
	// 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
		new_address = 0;
	}
}
