//==========================================================================
//
//      usbs_d12.c
//
//      Driver for the D12 USB Slave Board
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2006 Free Software Foundation, Inc.
//
// eCos is free software; you can redistribute it and/or modify it under    
// the terms of the GNU General Public License as published by the Free     
// Software Foundation; either version 2 or (at your option) any later      
// version.                                                                 
//
// eCos is distributed in the hope that it will be useful, but WITHOUT      
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or    
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License    
// for more details.                                                        
//
// You should have received a copy of the GNU General Public License        
// along with eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    Frank M. Pagliughi (fmp)
// Date:         2004-05-22
//
// This code is a device driver for the SoRo Systems USB-D12-104, a PC/104
// (ISA) Full-Speed USB slave board, which turns a PC/104 stack into a USB
// slave device. The board contains a Philips PDIUSBD12 Peripheral Controller
// Chip mapped into the PC's I/O space, with jumper-selectable I/O base 
// address, IRQ, and DMA settings. The eCos config tool is used to adjust
// settings for this driver to match the physical jumper settings. The chip
// could run in polled mode without an IRQ, but this wouldn't be a great idea
// other than maybe a debug environment. 
//
// The board supports DMA transfers over the Main endpoint, but I temporarily
// removed that code to make the driver portable to other platforms.
//
// *** This driver should also work with the Philips ISA Eval Board
//     for the D12, but I couldn't get one of them from Philips, so
//     I couldn't test it.
//
// The D12 uses an indexed register set, which it describes as "commands." 
// You first write a command (index) to the command register then you can
// read or write data to that register. Each multi-byte command read or write
// must be dione atomically, so all access to the chip must be serialized.
// 
// The D12 requests service through a single interrupt. The driver can
// be configured to service the chip through a DSR or a thread. In either
// case, the "service" code assumes it has unfettered access to the chip.
// The interrupt, therefore never touches the chip. It just schedules the
// DSR or service thread.
// Currently, the code gets exclusive access to the chip by locking the
// scheduler. This is suboptimal (locking the whole OS to touch one I/O 
// chip), and better method should be explored.
//
// This version of the driver does not support Isocronous transfers.
// 
// Additional notes on the D12:
//
// - The D12 has 4 endpoints (2 IN, and 2 OUT) in addition to the main 
//   control endpoint:
//   - Endp 0 (Control In & Out, 16 byte buffer)
//   - Endp 1 (IN & OUT, Bulk or Interrupt, 16 byte ea)
//   - Endp 2 (IN and/or OUT, Bulk, Interrupt, or Isoc, 64 bytes ea)
//
// - The "main" endpoint (as Philips calls it) is Endp 2. It's double
//   buffered and has a DMA interface, and thus, is suited for high
//   throughput. For applications that perform either Isoc In or Out,
//   the buffers for Endp 2 can be combined for a 128 byte space.
//   This driver, however, currently does not support this.
//
// - There may be a flaw in the double buffering of the rx main endpoint. 
//   According to the documentation it should be invisible to the software,
//   but if both buffers fill (on an rx/OUT), they must both be read 
//   together, otherwise it appears that the buffers/packets are returned
//   in reverse order. ReadMainEndpointBuf() returns the data properly.
//
// - All the interrupt sources on the chip - individual endpoints, bus reset,
//   suspend, and DMA - are OR'ed together and can be checked via the 
//   interrupt status register. When using edge-sensitive interrupts, as
//   we do here, the ISR/DSR must be sure all interrupts are cleared before
//   returning otherwise no new interrupts will be latched.
//
// - If the DMA controller is not used for the Main Endpoint, you MUST enable
//   the main endpoint interrupts in the DMA register (bits 6 & 7).
//   Personally, I think this should be the default at reset, to make it
//   compatible with the other endpoints, but Philips didn't see it that
//   way.
// 
// - When a Setup (Device Request) packet arrives in the control endpoint, a
//   bit is set in the endpoint's status register indicating the packet is
//   setup and not data. By the USB standard, a setup packet can not be
//   NAK'ed or STALL'ed, so when the chip receives a setup packet, it 
//   flushes the Ctrl (EP0) IN buffer and disables the Validate and Clear
//   Buffer commands. We must send an "acknowledge setup" to both
//   EP0 IN and OUT before a Validate or Clear Buffer command is effective.
//   See ReadSetupPacket().
//
//####DESCRIPTIONEND####
//==========================================================================

#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/infra/cyg_trac.h>
#include <cyg/infra/diag.h>

#include <pkgconf/devs_usb_d12.h>

#include <cyg/hal/drv_api.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_io.h>
#include <cyg/hal/hal_cache.h>
#include <cyg/error/codes.h>

#include <cyg/io/usb/usb.h>
#include <cyg/io/usb/usbs.h>

#include <string.h>

// --------------------------------------------------------------------------
// Common Types
// --------------------------------------------------------------------------

typedef cyg_uint8       byte;
typedef cyg_uint8       uint8;
typedef cyg_int16       int16;
typedef cyg_uint16      uint16;
typedef cyg_int32       int32;
typedef cyg_uint32      uint32;

// --------------------------------------------------------------------------
// Tracing & Debug
// --------------------------------------------------------------------------
// If the driver is configured to use a thread to service the chip, then it
// can also be configured to dump a lot of debug output.
// Care must be taken that USB timing requirements are not violated by 
// dumping debug info. If the data is sent to a serial port, it should use
// a hardware driver and have a large output buffer (115200 baud & 2kB
// buffer works for me).

#if defined(CYGFUN_DEVS_USB_D12_DEBUG) && CYGFUN_DEVS_USB_D12_DEBUG
#define TRACE_D12 diag_printf
#else
#define TRACE_D12 (1) ? (void)0 : diag_printf
#endif

#if defined(CYGSEM_DEVS_USB_D12_DEBUG_DUMP_EP0_BUFS) && CYGSEM_DEVS_USB_D12_DEBUG_DUMP_EP0_BUFS
#define TRACE_EP0       1
#endif

#if defined(CYGSEM_DEVS_USB_D12_DEBUG_DUMP_BUFS) && CYGSEM_DEVS_USB_D12_DEBUG_DUMP_BUFS
#define TRACE_EP        1
#endif

#if defined(TRACE_EP0) || defined(TRACE_EP)
static void _trace_buf(const char *hdr, const byte* buf, unsigned n)
{
  unsigned i;
  
  if (buf != 0 && n != 0) {
    if (hdr && hdr[0])
      TRACE_D12("%s ", hdr);
    
    TRACE_D12("[");
    for (i=0; i<n; i++) 
      TRACE_D12(" x%02X", buf[i]);
    TRACE_D12("]\n");
  }
}
#endif

#if defined(TRACE_EP0)
#define TRACE_BUF0      _trace_buf
#else   
#define TRACE_BUF0(hdr, buf, n)
#endif

#if defined(TRACE_EP)
#define TRACE_BUF       _trace_buf
#else   
#define TRACE_BUF(hdr, buf, n)
#endif

// ==========================================================================
// Chip Wrapper
// ==========================================================================

// This section contains functions that wrapper the low-level access to the 
// chip. There's a function around each register access on the chip, and then
// some.

#if defined(CYGSEM_DEVS_USB_D12_IO_MAPPED)
typedef void* d12_addr_type;
#else
typedef byte* d12_addr_type;
#endif

#define D12_BASE_ADDR   ((d12_addr_type) CYGNUM_DEVS_USB_D12_BASEADDR)

#define D12_ENDP0_SIZE       16 // Size of Ctrl Endp
#define D12_MAIN_ENDP         2 // The D12's "Main" Endp is special, double buffered
#define D12_MAIN_ENDP_SIZE   64 // Size of each main endp buffer
#define D12_MAX_PACKET_SIZE 128 // Max packet is actually double main endp

#define D12_CHIP_ID      0x1012 // Value that's returned by a read of
                                //the D12's Chip ID register

// ----- Endpoint Indices -----

enum {
  D12_ENDP_INVALID = 0xFF,
  D12_ENDP_MIN     = 0,
  
  D12_RX_CTRL_ENDP = D12_ENDP_MIN,                // Rx/Tx Nomenclature
  D12_TX_CTRL_ENDP,
  
  D12_RX_ENDP0     = D12_ENDP_MIN,
  D12_TX_ENDP0,
  D12_RX_ENDP1,
  D12_TX_ENDP1,
  D12_RX_ENDP2,
  D12_TX_ENDP2,
  D12_RX_MAIN_ENDP = D12_RX_ENDP2,
  D12_TX_MAIN_ENDP = D12_TX_ENDP2,
  
  
  D12_CTRL_ENDP_OUT = D12_ENDP_MIN,               // IN/OUT Nomenclature
  D12_CTRL_ENDP_IN,
  
  D12_ENDP0_OUT     = D12_ENDP_MIN,
  D12_ENDP0_IN,
  D12_ENDP1_OUT,
  D12_ENDP1_IN,
  D12_ENDP2_OUT,
  D12_ENDP2_IN,
  D12_MAIN_ENDP_OUT = D12_ENDP2_OUT,
  D12_MAIN_ENDP_IN  = D12_ENDP2_IN,
  
  D12_ENDP_INSERT_BEFORE,
  D12_ENDP_MAX      = D12_ENDP_INSERT_BEFORE-1
};

// ----- Set Mode Reg configuration byte -----

enum {  
  D12_MODE_CFG_NO_LAZYCLOCK       = 0x02,
  D12_MODE_CFG_CLOCK_RUNNING      = 0x04,
  D12_MODE_CFG_INTERRUPT          = 0x08,
  D12_MODE_CFG_SOFT_CONNECT       = 0x10,
  
  D12_MODE_CFG_NON_ISO            = 0x00,
  D12_MODE_CFG_ISO_OUT            = 0x40,
  D12_MODE_CFG_ISO_IN             = 0x80,
  D12_MODE_CFG_ISO_IO             = 0xC0,
  
  D12_MODE_CFG_DFLT               = (D12_MODE_CFG_NO_LAZYCLOCK |
				     D12_MODE_CFG_CLOCK_RUNNING | 
				     D12_MODE_CFG_NON_ISO)
};

// ----- Set Mode Reg clock div factor -----

enum {
  D12_MODE_CLK_24_MHZ                     = 1,
  D12_MODE_CLK_16_MHZ                     = 2,
  D12_MODE_CLK_12_MHZ                     = 3,
  D12_MODE_CLK_8_MHZ                      = 5,
  D12_MODE_CLK_6_MHZ                      = 7,
  D12_MODE_CLK_4_MHZ                      = 11,
  
  D12_MODE_CLK_DIV_MASK                   = 0x0F,
  
  D12_MODE_CLK_SET_TO_ONE                 = 0x40,
  D12_MODE_CLK_SOF_ONLY_INTR              = 0x80,
  
  D12_MODE_CLK_DFLT                       = (D12_MODE_CLK_4_MHZ | 
					     D12_MODE_CLK_SET_TO_ONE)
};

// ----- Set DMA Register -----

enum {
  D12_DMA_SINGLE_CYCLE,
  D12_DMA_BURST_4_CYCLE,
  D12_DMA_BURST_8_CYCLE,
  D12_DMA_BURST_16_CYCLE,
  
  D12_DMA_ENABLE                          = 0x04,
  D12_DMA_DIR_WRITE                       = 0x08,
  D12_DMA_DIR_READ                        = 0x00,
  D12_DMA_AUTO_RELOAD                     = 0x10,
  D12_DMA_INTR_PIN_MODE                   = 0x20,
  
  D12_DMA_MAIN_ENDP_OUT_INTR_ENABLE       = 0x40,
  D12_DMA_MAIN_RX_ENDP_INTR_ENABLE        = 0x40,
  
  D12_DMA_MAIN_ENDP_IN_INTR_ENABLE        = 0x80,
  D12_DMA_MAIN_TX_ENDP_INTR_ENABLE        = 0x80,
  
  D12_DMA_MAIN_ENDP_INTR_ENABLE           = 0xC0  // Enables IN & OUT Intr
};

// ----- Interrupt Register Bits -----

enum {
  D12_INTR_RX_CTRL_ENDP           = 0x0001,
  D12_INTR_TX_CTRL_ENDP           = 0x0002,
  
  D12_INTR_RX_ENDP0               = D12_INTR_RX_CTRL_ENDP,
  D12_INTR_TX_ENDP0               = D12_INTR_TX_CTRL_ENDP,
  D12_INTR_RX_ENDP1               = 0x0004,
  D12_INTR_TX_ENDP1               = 0x0008,
  D12_INTR_RX_ENDP2               = 0x0010,
  D12_INTR_TX_ENDP2               = 0x0020,
  
  D12_INTR_BUS_RESET              = 0x0040,
  D12_INTR_SUSPEND_CHANGE         = 0x0080,
  D12_INTR_DMA_EOT                = 0x0100
};

// ----- Read Endpoint Status -----

enum {
  D12_ENDP_STAT_SETUP_PACKET      = 0x04,
  D12_ENDP_STAT_BUF0_FULL         = 0x20,
  D12_ENDP_STAT_BUF1_FULL         = 0x40,
  D12_ENDP_STAT_ANY_BUF_FULL      = 0x60,
  D12_ENDP_STAT_BOTH_BUF_FULL     = 0x60,
  D12_ENDP_STAT_STALL             = 0x80,
};

// ----- Last Transaction Status Bits -----

enum {
  D12_LAST_TRANS_DATA_SUCCESS             = 0x01,
  D12_LAST_TRANS_ERR_CODE_MASK            = 0x1E,
  D12_LAST_TRANS_SETUP_PACKET             = 0x20,
  D12_LAST_TRANS_DATA1_PACKET             = 0x40,
  D12_LAST_TRANS_PREV_STAT_NOT_READ       = 0x80
};

static const byte RX_ENDP_INDEX[] = 
  { D12_RX_ENDP0, D12_RX_ENDP1, D12_RX_ENDP2 };
static const byte TX_ENDP_INDEX[] = 
  { D12_TX_ENDP0, D12_TX_ENDP1, D12_TX_ENDP2 };

static const int RX_ENDP_SIZE[] = { 16, 16, 64 };
static const int TX_ENDP_SIZE[] = { 16, 16, 64 };

typedef void (*completion_fn)(void*, int);

#ifndef USB_SETUP_PACKET_LEN 
#define USB_SETUP_PACKET_LEN    8
#endif

// ----- Command Definitions -----

enum {  
  CMD_SET_ADDR_EN                 = 0xD0,   // Write 1 byte
  CMD_SET_ENDP_EN                 = 0xD8,   // Write 1 byte
  CMD_SET_MODE                    = 0xF3,   // Write 2 bytes
  CMD_SET_DMA                     = 0xFB,   // Write/Read 1 byte
  CMD_READ_INTR_REG               = 0xF4,   // Read 2 bytes
  CMD_SEL_ENDP                    = 0x00,   // (+ Endp Index) Read 1 byte (opt)
  CMD_READ_LAST_TRANS_STAT        = 0x40,   // (+ Endp Index) Read 1 byte (opt)
  CMD_READ_ENDP_STAT              = 0x80,   // (+ Endp Index) Read 1 byte
  CMD_READ_BUF                    = 0xF0,   // Read n bytes
  CMD_WRITE_BUF                   = 0xF0,   // Write n bytes
  CMD_SET_ENDP_STAT               = 0x40,   // (+ Endp Index) Write 1 byte
  CMD_ACK_SETUP                   = 0xF1,   // None
  CMD_CLEAR_BUF                   = 0xF2,   // None
  CMD_VALIDATE_BUF                = 0xFA,   // None
  CMD_SEND_RESUME                 = 0xF6,   // None
  CMD_READ_CURR_FRAME_NUM         = 0xF5,   // Read 1 or 2 bytes
  CMD_READ_CHIP_ID                = 0xFD    // Read 2 bytes
};

// ----- Set Endpoint Enable Register -----

enum {
  ENDP_DISABLE,
  ENDP_ENABLE
};

// ----- Select Endpoint Results -----

enum {
  SEL_ENDP_FULL   = 0x01,
  SEL_ENDP_STALL  = 0x02
};

// ----- Error Codes from ReadLastTrans (need to be bit shifter) -----

enum {
  ERROR_NO_ERROR,
  ERROR_PID_ENCODING,
  ERROR_PID_UNKNOWN,
  ERROR_UNEXPECTED_PACKET,
  ERROR_TOKEN_CRC,
  ERROR_DATA_CRC,
  ERROR_TIMEOUT,
  ERROR_BABBLE,
  ERROR_UNEXPECTED_EOP,
  ERROR_NAK,
  ERROR_PACKET_ON_STALL,
  ERROR_OVERFLOW,
  ERROR_BITSTUFF,
  ERROR_WRONG_DATA_PID
};

// ------------------------------------------------------------------------
// Routines to access the D12 registers. The hardware specific driver 
// provides 8bit access functions and block access functions.

#include CYGIMP_DEVS_USB_D12_HW_ACCESS_HEADER


static inline uint16 
make_word(byte hi, byte lo)
{
  return ((uint16) hi << 8) | lo;
}

// These routines read or write 16 bit values to the data area.

static inline uint16 
d12_read_data_word(d12_addr_type base_addr)
{
  uint16 val = d12_read_data_byte(base_addr);
  val |= ((uint16) d12_read_data_byte(base_addr)) << 8;
  return val;
}

static inline void 
d12_write_data_word(d12_addr_type base_addr, uint16 val)
{
  d12_write_data_byte(base_addr, (byte) val);
  d12_write_data_byte(base_addr, (byte) (val >> 8));
}

// ------------------------------------------------------------------------
// Command & Data I/O
// ------------------------------------------------------------------------
//
// These routines read & write the registers in the D12. The procedure is
// to write a register/command value to the command address (A0=1) then
// read or write any required data a byte at a time to the data address
// (A0=0). The data can be one byte or two. If two, the low byte is read/
// written first.

// NOTE: These MUST be atomic operations. It's up to the caller 
//       to insure this.

// The hardware specific driver provides the basic access function.
//      

static inline void 
d12_write_byte(d12_addr_type base_addr, byte cmd, byte val)
{
  d12_write_cmd(base_addr, cmd);
  d12_write_data_byte(base_addr, val);
}

static inline void 
d12_write_word(d12_addr_type base_addr, byte cmd, uint16 val)
{
  d12_write_cmd(base_addr, cmd);
  d12_write_data_word(base_addr, val);
}

static inline byte 
d12_read_byte(d12_addr_type base_addr, byte cmd)
{
  d12_write_cmd(base_addr, cmd);
  return d12_read_data_byte(base_addr);
}

static inline uint16 
d12_read_word(d12_addr_type base_addr, byte cmd)
{
  d12_write_cmd(base_addr, cmd);
  return d12_read_data_word(base_addr);
}

// ------------------------------------------------------------------------
// Higher Level Commands
// ------------------------------------------------------------------------

// Stalls or Unstalls the endpoint. Bit0=1 for stall, =0 to unstall.

static inline void 
d12_set_endp_status(d12_addr_type base_addr, byte endp_idx, byte stat)
{
  d12_write_byte(base_addr, CMD_SET_ENDP_STAT + endp_idx, stat);
}

// ------------------------------------------------------------------------
// Stalls the control endpoint (both in & out).

static void 
d12_stall_ctrl_endp(d12_addr_type base_addr, bool stall)
{
  d12_set_endp_status(base_addr, D12_TX_CTRL_ENDP, stall ? 1 : 0);
  d12_set_endp_status(base_addr, D12_RX_CTRL_ENDP, stall ? 1 : 0);
}

// ------------------------------------------------------------------------
// Stalls/unstalls the specified endpoint. 

void inline 
d12_stall_endp(d12_addr_type base_addr, byte endp_idx, bool stall)
{
  d12_set_endp_status(base_addr, endp_idx, stall ? 1 : 0);
}

// ------------------------------------------------------------------------ */
// Tells the chip that the selected endpoint buffer has been completely
// read. This should be called after the application reads all the data
// from an endpoint.  While there's data in the buffer the chip will 
// automatically NAK any additional OUT packets from the host. 

static inline void 
d12_clear_buffer(d12_addr_type base_addr)
{
  d12_write_cmd(base_addr, CMD_CLEAR_BUF);
}

// ------------------------------------------------------------------------
// Tells the chip that the data in the selected endpoint buffer is complete
// and ready to be sent to the host.

static inline void 
d12_validate_buffer(d12_addr_type base_addr)
{
  d12_write_cmd(base_addr, CMD_VALIDATE_BUF);
}

// ------------------------------------------------------------------------
// Sends an upstream resume signal for 10ms. This command is normally 
// issued when the device is in suspend.

static inline void 
d12_send_resume(d12_addr_type base_addr)
{
  d12_write_cmd(base_addr, CMD_SEND_RESUME);
}

// ------------------------------------------------------------------------
// Gets the frame number of the last successfully received 
// start-of-frame (SOF).

static inline uint16 
d12_read_curr_frame_num(d12_addr_type base_addr)
{
  return d12_read_word(base_addr, CMD_READ_CURR_FRAME_NUM);
}

// ------------------------------------------------------------------------
// This routine acknowledges a setup packet by writing an Ack Setup command
// to the currently selected Endpoint. This must be done for both EP0 out
// and EP0 IN whenever a setup packet is received.

static inline void 
d12_ack_setup(d12_addr_type base_addr)
{
  d12_write_cmd(base_addr, CMD_ACK_SETUP);
}

// ------------------------------------------------------------------------
// Gets the value of the 16-bit interrupt register, which indicates the 
// source of an interrupt (if interrupts are not used, this reg can be 
// polled to find when service is required).

static inline uint16 
d12_read_intr_reg(d12_addr_type base_addr)
{
  return d12_read_word(base_addr, CMD_READ_INTR_REG) & 0x01FF;
}

// ------------------------------------------------------------------------
// Gets/Sets the contents of the DMA register.

static inline byte 
d12_get_dma(d12_addr_type base_addr)
{
  return d12_read_byte(base_addr, CMD_SET_DMA);
}

static inline void 
d12_set_dma(d12_addr_type base_addr, byte mode)
{
  d12_write_byte(base_addr, CMD_SET_DMA, mode);
}

// ------------------------------------------------------------------------
// Sends the "Select Endpoint" command (0x00 - 0x0D) to the chip.
// This command initializes an internal pointer to the start of the 
// selected buffer.
// 
// Returns: Bitfield containing status of the endpoint

static byte 
d12_select_endp(d12_addr_type base_addr, byte endp_idx)
{
  return d12_read_byte(base_addr, CMD_SEL_ENDP + endp_idx);
}

// ------------------------------------------------------------------------
// Gets the status of the last transaction of the endpoint. It also resets 
// the corresponding interrupt flag in the interrupt register, and clears 
// the status, indicating that it was read.
//
// Returns: Bitfield containing the last transaction status.

static inline byte 
d12_read_last_trans_status(d12_addr_type base_addr, byte endp_idx)
{
  return d12_read_byte(base_addr, CMD_READ_LAST_TRANS_STAT + endp_idx);
}

// ------------------------------------------------------------------------
// Reads the status of the requested endpoint. 
// Just for the heck of it, we mask off the reserved bits.
//
// Returns: Bitfield containing the endpoint status.

static inline byte 
d12_read_endp_status(d12_addr_type base_addr, byte endp_idx)
{
  return d12_read_byte(base_addr, CMD_READ_ENDP_STAT + endp_idx) & 0xE4;
}

// ------------------------------------------------------------------------
// Returns true if there is data available in the specified endpoint's
// ram buffer. This is determined by the buf full flags in the endp status
// register.

static inline bool 
d12_data_available(d12_addr_type base_addr, byte endp_idx)
{
  byte by = d12_read_endp_status(base_addr, endp_idx);
  return (bool) (by & D12_ENDP_STAT_ANY_BUF_FULL);
}

// ------------------------------------------------------------------------
// Clears the transaction status for each of the endpoints by calling the
// d12_read_last_trans_status() function for each. 

static void 
d12_clear_all_intr(d12_addr_type base_addr)
{
  uint8 endp;
  
  d12_read_intr_reg(base_addr);
  
  for (endp=D12_ENDP_MIN; endp<=D12_ENDP_MAX; ++endp)
    d12_read_last_trans_status(base_addr, endp);
}

// ------------------------------------------------------------------------
// Loads a value into the Set Address / Enable register. This sets the 
// device's USB address (lower 7 bits) and enables/disables the function
// (msb).

static void 
d12_set_addr_enable(d12_addr_type base_addr, byte usb_addr, bool enable)
{
  if (enable) 
    usb_addr |= 0x80;
  
  d12_write_byte(base_addr, CMD_SET_ADDR_EN, usb_addr);
}

// ------------------------------------------------------------------------
// Enables/disables the generic endpoints.

static inline void 
d12_set_endp_enable(d12_addr_type base_addr, bool enable)
{
  d12_write_byte(base_addr, CMD_SET_ENDP_EN, 
		 (enable) ? ENDP_ENABLE : ENDP_DISABLE);
}

// ------------------------------------------------------------------------
// Sets the device's configuration and CLKOUT frequency.

static void 
d12_set_mode(d12_addr_type base_addr, byte config, byte clk_div)
{
  uint16 w = make_word(clk_div, config);
  d12_write_word(base_addr, CMD_SET_MODE, w);
}

// ------------------------------------------------------------------------
// Reads a setup packet from the control endpoint. This procedure is 
// somewhat different than reading a data packet. By the USB standard, a 
// setup packet can not be NAK'ed or STALL'ed, so when the chip receives a 
// setup packet, it flushes the Ctrl (EP0) IN buffer and disables the 
// Validate and Clear Buffer commands. The processor must send an 
// acknowledge setup to both EP0 IN and OUT before a Validate or Clear
// Buffer command is effective.
//
// Parameters:
//      buf     buffer to receive the contents of the setup packet. Must
//              be at least 8 bytes.
// Returns:
//      true    if there are 8 bytes waiting in the EP0 OUT RAM buffer
//              on the D12 (i.e., true if successful)
//      false   otherwise

static bool 
d12_read_setup_packet(d12_addr_type base_addr, byte *buf)
{
  uint8 n;
  
  d12_select_endp(base_addr, D12_RX_CTRL_ENDP);
  
  d12_read_byte(base_addr, CMD_READ_BUF);   // Read & discard reserved byte
  n = d12_read_data_byte(base_addr);        // # bytes available
  
  if (n > USB_SETUP_PACKET_LEN) {
    //TRACE("* Warning: Setup Packet too large: %u *\n", (unsigned) n);
    n = USB_SETUP_PACKET_LEN;
  }
  
  n = d12_read_data(base_addr, buf, n);
  
  d12_ack_setup(base_addr);
  d12_clear_buffer(base_addr);
  
  // ----- Ack Setup to EP0 IN ------
  
  d12_select_endp(base_addr, D12_TX_CTRL_ENDP);
  d12_ack_setup(base_addr);
  
  return n == USB_SETUP_PACKET_LEN;
}

// ------------------------------------------------------------------------
// Reads the contents of the currently selected endpoint's RAM buffer into 
// the buf[] array.
//
// The D12's buffer comes in as follows:
//     [0]     junk ("reserved" - can be anything). Just disregard
//     [1]     # data bytes to follow
//     [2] data byte 0, ...
//     up to
//     [N+2] data byte N-1
//
// Parameters:
//      buf  byte array to receive data. This MUST be at least the size
//           of the chip's RAM buffer for the currently selected endpoint.
//           If buf is NULL, the data is read & discarded.
//
// Returns: the actual number of bytes read (could be <= n)

static uint8 
d12_read_selected_endp_buf(d12_addr_type base_addr, byte *buf)
{
  uint8 n;

  d12_read_byte(base_addr, CMD_READ_BUF);   // Read & discard reserved byte
  n = d12_read_data_byte(base_addr);        // # bytes in chip's buf
  d12_read_data(base_addr, buf, n);
  d12_clear_buffer(base_addr);

  return n;
}

// ------------------------------------------------------------------------
// Selects the specified endpoint and reads the contents of it's RAM buffer
// into the buf[] array. For the Main OUT endpoint, it will check whether 
// both buffers are full, and if so, read them both.
//
// Side Effects:
//              - Leaves endp_idx as the currently selected endpoint.
//
// Parameters:
//      endp_idx    the endpoint from which to read
//      buf         buffer to receive the data. This MUST be at least the size
//                  of the chip's RAM buffer for the specified endpoint.
//                  For the Main endp, it must be 2x the buffer size (128 total)
//
// Returns: the # of bytes read.

static uint8 
d12_read_endp_buf(d12_addr_type base_addr, byte endp_idx, byte *buf)
{
  return (d12_select_endp(base_addr, endp_idx) & SEL_ENDP_FULL)
    ? d12_read_selected_endp_buf(base_addr, buf) : 0;
}

// ------------------------------------------------------------------------
// Does a read of the "main" endpoint (#2). Since it's double buffered,
// this will check if both buffers are full, and if so it will read them
// both. Thus the caller's buffer, buf, must be large enough to hold all
// the data - 128 bytes total.
// 
// If either buffer contains less than the full amount, the done flag
// is set indicating that a Bulk OUT transfer is complete.
// 
// This determines if a bulk transfer is done, since the caller can't 
// necessarily determine this from the size of the return buffer.
// If either buffer is less than full, '*done' is set to a non-zero value.

static uint8 
d12_read_main_endp_buf(d12_addr_type base_addr, byte *buf, int *done)
{
  int             nBuf = 1;
  uint8   n = 0;
  byte    stat = d12_read_endp_status(base_addr, D12_RX_MAIN_ENDP) & 
    D12_ENDP_STAT_ANY_BUF_FULL;
  
  if (stat == 0)
    return 0;
  
  if (stat == D12_ENDP_STAT_BOTH_BUF_FULL)
    nBuf++;
  
  *done = false;
  
  while (nBuf--) {
    if (d12_select_endp(base_addr, D12_RX_MAIN_ENDP) & SEL_ENDP_FULL) {
      uint8 n1 = d12_read_selected_endp_buf(base_addr, buf+n);
      n += n1;
      if (n1 < D12_MAIN_ENDP_SIZE) {
	*done = true;
	break;
      }
    }
    else
      *done = true;
  }
  return n;
}

// ------------------------------------------------------------------------
// Writes the contents of the buf[] array to the currently selected 
// endpoint's RAM buffer. The host will get the data on the on the next IN
// packet from the endpoint.
//
// Note:
//      - The length of the buffer, n, must be no more than the size of the
//      endpoint's RAM space, though, currently, this is not checked.
//      - It's feasible that the application needs to send an empty (NULL) 
//      packet. It's valid for 'n' to be zero, and/or buf NULL.

static uint8 
d12_write_selected_endp_buf(d12_addr_type base_addr, const byte *buf, uint8 n)
{
  d12_write_byte(base_addr, CMD_WRITE_BUF, 0);
  d12_write_data_byte(base_addr, n);
  d12_write_data(base_addr, buf, n);
  d12_validate_buffer(base_addr);
  
  return n;
}

// ------------------------------------------------------------------------
// Writes the contents of the buf[] array to the specified endoint's RAM
// buffer. The host will get this data on the next IN packet from the 
// endpoint.
//
// Side Effects:
//      - Leaves endp_idx as the currently selected endpoint.

static uint8 
d12_write_endp_buf(d12_addr_type base_addr, byte endp_idx, 
		   const byte *buf, uint8 n)
{
  d12_select_endp(base_addr, endp_idx);
  return d12_write_selected_endp_buf(base_addr, buf, n);
}

// ------------------------------------------------------------------------
// Reads & returns the contents of the Chip ID register.

static inline uint16 
d12_read_chip_id(d12_addr_type base_addr)
{
  return d12_read_word(base_addr, CMD_READ_CHIP_ID);
}


// ==========================================================================
// eCos-Specific Device Driver Code
// ==========================================================================

static void usbs_d12_reset(void);

// Make some abbreviations for the configuration options.

#if defined(CYGPKG_DEVS_USB_D12_RX_EP1)
#define _RX_EP1
#endif

#if defined(CYGPKG_DEVS_USB_D12_TX_EP1)
#define _TX_EP1
#endif

#if defined(CYGPKG_DEVS_USB_D12_RX_EP2)
#define _RX_EP2
#endif

#if defined(CYGPKG_DEVS_USB_D12_TX_EP2)
#define _TX_EP2
#endif

// --------------------------------------------------------------------------
// Endpoint 0 Data
// --------------------------------------------------------------------------

static cyg_interrupt    usbs_d12_intr_data;
static cyg_handle_t     usbs_d12_intr_handle;

static byte ep0_tx_buffer[CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE];

static void usbs_d12_start(usbs_control_endpoint*);
static void usbs_d12_poll(usbs_control_endpoint*);

typedef enum endp_state {
  ENDP_STATE_IDLE,
  ENDP_STATE_IN,
  ENDP_STATE_OUT
} endp_state;

typedef struct ep0_impl {
  usbs_control_endpoint   common;
  endp_state              ep_state;
  int                     length;
  int                     transmitted;
  bool                    tx_empty;
} ep0_impl;

static ep0_impl ep0 = {
 common:
 {
 state:                  USBS_STATE_POWERED,
 enumeration_data:       (usbs_enumeration_data*) 0,
 start_fn:               &usbs_d12_start,
 poll_fn:                &usbs_d12_poll,
 interrupt_vector:       CYGNUM_DEVS_USB_D12_IRQ,
 control_buffer:         { 0, 0, 0, 0, 0, 0, 0, 0 },
 state_change_fn:        0,
 state_change_data:      0,
 standard_control_fn:    0,
 standard_control_data:  0,
 class_control_fn:       0,
 class_control_data:     0,
 vendor_control_fn:      0,
 vendor_control_data:    0,
 reserved_control_fn:    0,
 reserved_control_data:  0,
 buffer:                 0,
 buffer_size:            0,
 fill_buffer_fn:         0,
 fill_data:              0,
 fill_index:             0,
 complete_fn:            0
 },
 ep_state:               ENDP_STATE_IDLE,
 length:                 0,
 transmitted:    0,
 tx_empty:               0
};

extern usbs_control_endpoint usbs_d12_ep0 __attribute__((alias ("ep0")));

// --------------------------------------------------------------------------
// Rx Endpoints 1 & 2 Data
// --------------------------------------------------------------------------

#if defined(_RX_EP1) || defined(_RX_EP2)

typedef struct rx_endpoint {
  usbs_rx_endpoint        common;
  int                     endp, received;
} rx_endpoint;

static void usbs_d12_api_start_rx_ep(usbs_rx_endpoint*);
static void usbs_d12_api_stall_rx_ep(usbs_rx_endpoint*, cyg_bool);

static void usbs_d12_ep_rx_complete(rx_endpoint *ep, int result);
static void usbs_d12_stall_rx_ep(rx_endpoint*, cyg_bool);

#endif


#if defined(_RX_EP1)

static rx_endpoint rx_ep1 = {
 common: {
  start_rx_fn:    &usbs_d12_api_start_rx_ep,
  set_halted_fn:  &usbs_d12_api_stall_rx_ep,
  halted:         0
 },
 endp:            1
};

extern usbs_rx_endpoint usbs_d12_rx_ep1 __attribute__((alias ("rx_ep1")));

#endif


#if defined(_RX_EP2)

static rx_endpoint rx_ep2 = {
 common: {
  start_rx_fn:    &usbs_d12_api_start_rx_ep,
  set_halted_fn:  &usbs_d12_api_stall_rx_ep,
  halted:         0
 },
 endp:            2
};

extern usbs_rx_endpoint usbs_d12_rx_ep2 __attribute__((alias ("rx_ep2")));

#endif

// --------------------------------------------------------------------------
// Tx Endpoints 1 & 2 Data
// --------------------------------------------------------------------------

#if defined(_TX_EP1) || defined(_TX_EP2)

typedef struct tx_endpoint {
  usbs_tx_endpoint        common;
  int                     endp, transmitted;
  bool                    tx_empty;
} tx_endpoint;

static void usbs_d12_api_start_tx_ep(usbs_tx_endpoint*);
static void usbs_d12_api_stall_tx_ep(usbs_tx_endpoint*, cyg_bool);

static void usbs_d12_ep_tx_complete(tx_endpoint *ep, int result);
static void usbs_d12_stall_tx_ep(tx_endpoint*, cyg_bool);
#endif

#if defined(_TX_EP1)

static tx_endpoint tx_ep1 = {
 common: {
  start_tx_fn:    &usbs_d12_api_start_tx_ep,
  set_halted_fn:  &usbs_d12_api_stall_tx_ep,
  halted:         0
 },
 endp:            1
};

extern usbs_tx_endpoint usbs_d12_tx_ep1 __attribute__((alias ("tx_ep1")));
#endif

#if defined(_TX_EP2)

static tx_endpoint tx_ep2 = {
 common: {
  start_tx_fn:    &usbs_d12_api_start_tx_ep,
  set_halted_fn:  &usbs_d12_api_stall_tx_ep,
  halted:         0
 },
 endp:            2
};

extern usbs_tx_endpoint usbs_d12_tx_ep2 __attribute__((alias ("tx_ep2")));

#endif

// --------------------------------------------------------------------------
// Synchronization

static inline void usbs_d12_lock(void)          { cyg_scheduler_lock(); }
static inline void usbs_d12_unlock(void)        { cyg_scheduler_unlock(); }

// --------------------------------------------------------------------------
// Control Endpoint
// --------------------------------------------------------------------------

// Fills the EP0 transmit buffer with a packet. Partial data packets are 
// retrieved by repeatedly calling the fill function.

static int 
ep0_fill_tx_buffer(void)
{
  int nFilled = 0;
  
  while (nFilled < CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE) {
    if (ep0.common.buffer_size != 0) {
      if ((nFilled + ep0.common.buffer_size) < 
	  CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE) {
	memcpy(&ep0_tx_buffer[nFilled], ep0.common.buffer, 
	       ep0.common.buffer_size);
	nFilled += ep0.common.buffer_size;
	ep0.common.buffer_size = 0;
      }
      else {
	break;
      }
    }
    else if (ep0.common.fill_buffer_fn) {
      (*ep0.common.fill_buffer_fn)(&ep0.common);
    }
    else {
      break;
    }
  }
  CYG_ASSERT((ep0.common.buffer_size == 0) && (!ep0.common.fill_buffer_fn), 
	     "EP0 transmit buffer overflow");
  TRACE_D12("EP0: Filled Tx Buf with %d bytes\n", nFilled);
  
  ep0.length = nFilled;
  
  ep0.common.fill_buffer_fn       = 0;
  ep0.common.fill_data            = 0;
  ep0.common.fill_index           = 0;
  
  return nFilled;
}

// --------------------------------------------------------------------------
// Called when a transfer is complete on the control endpoint EP0. 
// It resets the endpoint's data structure and calls the completion function,
// if any.
//
// PARAMETERS:
// result          0, on success
//                 -EPIPE or -EIO to indicate a cancellation

static usbs_control_return 
usbs_d12_ep0_complete(int result)
{
  usbs_control_return ret = USBS_CONTROL_RETURN_UNKNOWN;
  
  ep0.ep_state = ENDP_STATE_IDLE;
  
  if (ep0.common.complete_fn)
    ret = (*ep0.common.complete_fn)(&ep0.common, result);
  
  ep0.common.buffer                       = 0;
  ep0.common.buffer_size          = 0;
  ep0.common.complete_fn          = 0;
  //ep0.common.fill_buffer_fn     = 0;
  
  return ret;
}

// --------------------------------------------------------------------------
// This routine is called when we want to send the next packet to the tx ep0
// on the chip. It is used to start a new transfer, and is also called when
// the chip interrupts to indicate that the ep0 tx buffer is empty and ready
// to receive a new packet.
//
// NOTE:
//      On the D12, when you send a zero-length packet to a tx endpoint, the
//      chip transmits the empty packet to the host, but doesn't interrupt 
//      indicating that it is complete. So immediately after sending the
//      empty packet we complete the transfer.

static void 
usbs_d12_ep0_tx(void)
{
  int     nRemaining = ep0.length - ep0.transmitted;
  uint8   n;
  
  // ----- Intermittent interrupt? Get out -----
  
  if (!ep0.common.buffer) {
    TRACE_D12("EP0: Tx no buffer (%d)\n", nRemaining);
    return;
  }
  
  // ----- If prev packet was last, signal that we're done -----
  
  if (nRemaining == 0 && !ep0.tx_empty) {
    TRACE_D12("\tEP0: Tx Complete (%d) %p\n", ep0.transmitted, 
	      ep0.common.complete_fn);
    usbs_d12_ep0_complete(0);
    return;
  }
  
  // ----- Load the next tx packet onto the chip -----
  
  if (nRemaining < D12_ENDP0_SIZE) {
    n = (uint8) nRemaining;
    ep0.tx_empty = false;
  }
  else
    n = D12_ENDP0_SIZE;
  
  d12_write_endp_buf(D12_BASE_ADDR, D12_TX_ENDP0, 
		     &ep0_tx_buffer[ep0.transmitted], n);
  
  TRACE_D12("EP0: Wrote %u bytes\n", (unsigned) n);
  TRACE_BUF0("\t", &ep0_tx_buffer[ep0.transmitted], n);
  
  ep0.transmitted += n;
  
  // ----- If empty packet, D12 won't interrupt, so end now ----- */
  
  if (n == 0) {
    TRACE_D12("\tEP0: Tx Complete (%d) %p\n", ep0.transmitted, 
	      ep0.common.complete_fn);
    usbs_d12_ep0_complete(0);
  }
}

// --------------------------------------------------------------------------
// This function is called when a packet has been successfully sent on the
// primary control endpoint (ep0). It indicates that the chip is ready for 
// another packet. We read the LastTransStatus for the endpoint to clear 
// the interrupt bit, then call ep0_tx() to continue the transfer.

static void 
usbs_d12_ep0_tx_intr(void)
{
  d12_read_last_trans_status(D12_BASE_ADDR, D12_TX_ENDP0);
  usbs_d12_ep0_tx();
}

// --------------------------------------------------------------------------
// Try to handle standard requests. This is a three step process:
//     1.   If it's something we should handle internally we take care of it.
//          Currently we can handle SET_ADDRESS requests, and a few others.
//     2.   If the upper level code has installed a standard control handler
//          we let that function have a crack at it.
//     3.   If neither of those handle the packet we let 
//          usbs_handle_standard_control() have a last try at it.
//
// Locally:
//          SET_ADDRESS: The host is demanding that we change our USB address.
//          This is done by updating the Address/Enable register on the D12. 
//          Note, however that the USB protocol requires us to ack at the old 
//          address, change address, and then accept the next control message
//          at the new      address. The D12 address reg is buffered to do this 
//          automatically for us. The updated address on the chip won't take
//          affect until after the empty ack is sent. Nice.
//

static usbs_control_return 
usbs_d12_handle_std_req(usb_devreq *req)
{
  usbs_control_return result = USBS_CONTROL_RETURN_UNKNOWN;
  int recipient = req->type & USB_DEVREQ_RECIPIENT_MASK;

  if (req->request == USB_DEVREQ_SET_ADDRESS) {
    TRACE_D12("Setting Addr: %u\n", (unsigned) req->value_lo);
    d12_set_addr_enable(D12_BASE_ADDR, req->value_lo, true);
    result = USBS_CONTROL_RETURN_HANDLED;
  }
  else if (req->request == USB_DEVREQ_GET_STATUS) {
    if (recipient == USB_DEVREQ_RECIPIENT_DEVICE) {
      const usbs_enumeration_data *enum_data = ep0.common.enumeration_data;
      if (enum_data && enum_data->device.number_configurations == 1 &&
	  enum_data->configurations) {
	ep0.common.control_buffer[0]  = 
	  (enum_data->configurations[0].attributes
	   & USB_CONFIGURATION_DESCRIPTOR_ATTR_SELF_POWERED) ? 1 : 0;
	ep0.common.control_buffer[0] |= 
	  (enum_data->configurations[0].attributes
	   & USB_CONFIGURATION_DESCRIPTOR_ATTR_REMOTE_WAKEUP) ? 2 : 0;
	ep0.common.control_buffer[1] = 0;
	result = USBS_CONTROL_RETURN_HANDLED;
      }
    }
    else if (recipient == USB_DEVREQ_RECIPIENT_ENDPOINT) {
      bool halted = false;
      result = USBS_CONTROL_RETURN_HANDLED;

      switch (req->index_lo) {
#if defined(_RX_EP1)
      case 0x01 : halted = rx_ep1.common.halted;      break;
#endif
#if defined(_TX_EP1)
      case 0x81 : halted = tx_ep1.common.halted;      break;
#endif
#if defined(_RX_EP2)
      case 0x02 : halted = rx_ep2.common.halted;      break;
#endif
#if defined(_TX_EP2)
      case 0x82 : halted = tx_ep2.common.halted;      break;
#endif

      default:
	result = USBS_CONTROL_RETURN_STALL;
      }

      TRACE_D12("Get Status: Endp [0x%02X] %s\n", (unsigned) req->index_lo, 
		halted ? "Halt" : "Unhalt");
      if (result == USBS_CONTROL_RETURN_HANDLED) {
	ep0.common.control_buffer[0] = (halted) ? 1 : 0;
	ep0.common.control_buffer[1] = 0;
      }
    }

    if (result == USBS_CONTROL_RETURN_HANDLED) {
      ep0.common.buffer                       = ep0.common.control_buffer;
      ep0.common.buffer_size                  = 2;
      ep0.common.fill_buffer_fn               = 0;
      ep0.common.complete_fn                  = 0;
    }
  }
  else if ((req->request == USB_DEVREQ_SET_FEATURE || 
	    req->request == USB_DEVREQ_CLEAR_FEATURE) && 
	   recipient == USB_DEVREQ_RECIPIENT_ENDPOINT) {
    
    bool halt = (req->request == USB_DEVREQ_SET_FEATURE);
    result = USBS_CONTROL_RETURN_HANDLED;
    TRACE_D12("Endpoint [0x%02X] %s\n", (unsigned) req->index_lo, 
	      halt ? "Halt" : "Unhalt");

    switch (req->index_lo) {
#if defined(_RX_EP1)
    case 0x01 :     usbs_d12_stall_rx_ep(&rx_ep1, halt);    break;
#endif
#if defined(_TX_EP1)
    case 0x81 : usbs_d12_stall_tx_ep(&tx_ep1, halt);        break;
#endif
#if defined(_RX_EP2)
    case 0x02 :     usbs_d12_stall_rx_ep(&rx_ep2, halt);    break;
#endif
#if defined(_TX_EP2)
    case 0x82 : usbs_d12_stall_tx_ep(&tx_ep2, halt);        break;
#endif
      
    default:
      result = USBS_CONTROL_RETURN_STALL;
    }
  }
  else if (ep0.common.standard_control_fn != 0) {
    result = (*ep0.common.standard_control_fn)
      (&ep0.common,
       ep0.common.standard_control_data);
  }
  
  if (result == USBS_CONTROL_RETURN_UNKNOWN)
    result = usbs_handle_standard_control(&ep0.common);
  
  return result;
}

// --------------------------------------------------------------------------
// Handler for the receipt of a setup (dev request) packet from the host.
// We examine the packet to determine what function(s) should get a crack
// at trying to handle it, then pass control to the proper function. If
// the function handles the message we either ACK (len==0) or prepare for
// an IN or OUT data phase. If no one handled the message, we stall the
// control endpoint.

static void 
usbs_d12_ep0_setup_packet(usb_devreq* req)
{
  int             len, dir, protocol, recipient;
  usbs_control_return     result = USBS_CONTROL_RETURN_UNKNOWN;
  
  // ----- See who should take the request -----
  
  len = make_word(req->length_hi, req->length_lo);
  
  dir                     = req->type & USB_DEVREQ_DIRECTION_MASK;
  protocol    = req->type & USB_DEVREQ_TYPE_MASK;
  recipient   = req->type & USB_DEVREQ_RECIPIENT_MASK;
  
  TRACE_BUF0("DevReq: ", ep0.common.control_buffer, sizeof(usb_devreq));
  
  if (protocol == USB_DEVREQ_TYPE_STANDARD)
    result = usbs_d12_handle_std_req(req);
  else {
    // Pass on non-standard requests to registered handlers
    
    usbs_control_return     (*callback_fn)(usbs_control_endpoint*, void*);
    void *callback_arg;
    
    if (protocol == USB_DEVREQ_TYPE_CLASS) {
      callback_fn  = ep0.common.class_control_fn;
      callback_arg = ep0.common.class_control_data;
    }
    else if (protocol == USB_DEVREQ_TYPE_VENDOR) {
      callback_fn  = ep0.common.vendor_control_fn;
      callback_arg = ep0.common.vendor_control_data;
    }
    else {
      callback_fn  = ep0.common.reserved_control_fn;
      callback_arg = ep0.common.reserved_control_data;
    }
    
    result = (callback_fn)  ? (*callback_fn)(&ep0.common, callback_arg)
      : USBS_CONTROL_RETURN_STALL;
  }
  
  // ----- If handled prep/handle data phase, otherwise stall -----
  
  if (result == USBS_CONTROL_RETURN_HANDLED) {
    if (len == 0) {
      TRACE_D12("\tCtrl ACK\n");
      d12_write_endp_buf(D12_BASE_ADDR, D12_TX_ENDP0, 0, 0);
    }
    else {
      // Set EP0 state to  IN or OUT mode for data phase
      ep0.transmitted = 0;
      ep0.length = len;
      
      if (dir == USB_DEVREQ_DIRECTION_OUT) {
	// Wait for the next packet from the host.
	ep0.ep_state = ENDP_STATE_OUT;
	CYG_ASSERT(ep0.common.buffer != 0, 
		   "A rx buffer should have been provided for EP0");
	CYG_ASSERT(ep0.common.complete_fn != 0, 
		   "A completion function should be provided for EP0 OUT control messages");
      }
      else {
	ep0.tx_empty = true;
	ep0.ep_state = ENDP_STATE_IN;
	ep0_fill_tx_buffer();
	usbs_d12_ep0_tx();
      }
    }
  }
  else {
    TRACE_D12("\t*** Unhandled Device Request ***\n");
    // The request wasn't handled, so stall control endpoint
    d12_stall_ctrl_endp(D12_BASE_ADDR, true);
  }
}

// --------------------------------------------------------------------------
// This is called when the chip indicates that a packet has been received
// on control endpoint 0. If it's a setup packet, we handle it accordingly,
// otherwise it's a data packet coming in on ep0.
//

static void 
usbs_d12_ep0_rx_intr(void)
{
  byte byStat = d12_read_last_trans_status(D12_BASE_ADDR, D12_RX_ENDP0);
  TRACE_D12("\tEP0 Status: 0x%02X\n", (unsigned) byStat);
  
  if (byStat & D12_LAST_TRANS_SETUP_PACKET) {
    usb_devreq *req = (usb_devreq *) ep0.common.control_buffer;
    
    if (!d12_read_setup_packet(D12_BASE_ADDR, (byte*) req)) {
      TRACE_D12("ep0_rx_dsr: Error reading setup packet\n");
      d12_stall_ctrl_endp(D12_BASE_ADDR, true);
    }
    else
      usbs_d12_ep0_setup_packet(req);
  }
  else {
    if (ep0.common.buffer) {
      uint8 n = d12_read_endp_buf(D12_BASE_ADDR, D12_RX_ENDP0, 
				  ep0.common.buffer + ep0.transmitted);
      ep0.transmitted += n;
      
      TRACE_D12("EP0: Received %d bytes\n", (unsigned) n);
      
      if (n < D12_ENDP0_SIZE || 
	  ep0.common.buffer_size - ep0.transmitted < D12_ENDP0_SIZE) {
	TRACE_D12("\tEP0: Rx Complete (%d) %p\n", 
		  ep0.transmitted, ep0.common.complete_fn);
	
	if (usbs_d12_ep0_complete(0) == USBS_CONTROL_RETURN_HANDLED)
	  d12_write_endp_buf(D12_BASE_ADDR, D12_TX_ENDP0, 0, 0);
	else
	  d12_stall_ctrl_endp(D12_BASE_ADDR, true);
      }
    }
    else {
      TRACE_D12("EP0: No Rx buffer. Discarding packet\n");
      d12_read_endp_buf(D12_BASE_ADDR, D12_RX_ENDP0, NULL);
    }
  }
}

// --------------------------------------------------------------------------
// Handler for when the device is put into or taken out of suspend mode.
// It updates the state variable in the control endpoint and calls the
// registered state change function, if any.

// TODO: Put the chip into low power mode??? Stop clocks, etc???

static void 
usbs_d12_suspend(bool suspended)
{
  int                     old_state = ep0.common.state;
  usbs_state_change       state_change;
  
  if (suspended) {
    ep0.common.state |= USBS_STATE_SUSPENDED;
    state_change = USBS_STATE_CHANGE_SUSPENDED;
  }
  else {
    ep0.common.state &= USBS_STATE_MASK;
    state_change = USBS_STATE_CHANGE_RESUMED;
  }
  
  if (ep0.common.state_change_fn) {
    (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data,
				  state_change, old_state);
  }
}

// --------------------------------------------------------------------------
// Common Rx Endpoint 1 & 2
// --------------------------------------------------------------------------

#if defined(_RX_EP1) || defined(_RX_EP2)

static void usbs_d12_clear_rx_ep(rx_endpoint *ep)
{
  ep->common.buffer               = 0;
  ep->common.buffer_size          = 0;
  ep->common.complete_fn          = 0;
  ep->common.complete_data        = 0;
  
  ep->received                    = 0;
}

// --------------------------------------------------------------------------
// This is called when an rx operation is completed. It resets the endpoint
// vars and calls the registered completion function.
//

static void 
usbs_d12_ep_rx_complete(rx_endpoint *ep, int result)
{
  completion_fn fn = ep->common.complete_fn;
  void *data = ep->common.complete_data;
  
  usbs_d12_clear_rx_ep(ep);
  
  if (fn)
    (*fn)(data, result);
}

// --------------------------------------------------------------------------
// This routine is called when an rx buffer in the chip is full and ready to
// be read. If there's an endpoint buffer available and room to hold the data
// we read it in, otherwise we call the completion function, but leave the 
// data in the chip. The hardware will automatically NAK packages from the
// host until the app calls another start read to continue receiving data.
//
// CONTEXT:
//         Called from either the DSR or application thread, via start rx.
//         In either case, it's assumed that the chip is locked.
//              

static void 
usbs_d12_ep_rx(rx_endpoint *ep)
{
  int             n, ep_size, buf_remaining, endp = ep->endp;
  bool    done;
  
  // The main endp is double buffered and we need to be prepared
  // to read both simultaneously.
  ep_size = (endp == D12_MAIN_ENDP) ? (2 * D12_MAIN_ENDP_SIZE) 
    : RX_ENDP_SIZE[endp];
  
  buf_remaining = ep->common.buffer_size - ep->received;
  
  // ----- If no space left in buffer, call completion fn -----
  
  if (!ep->common.buffer || buf_remaining < ep_size) {
    int ret = ep->received;
    
    // See if caller requested a read smaller than the endp. Read &
    // throw away extra
    if (ep->common.buffer_size < ep_size) {
      byte tmp_buf[D12_MAX_PACKET_SIZE];
      
      if (endp == D12_MAIN_ENDP)
	n = d12_read_main_endp_buf(D12_BASE_ADDR, tmp_buf, &done);
      else
	n = d12_read_endp_buf(D12_BASE_ADDR, RX_ENDP_INDEX[endp], tmp_buf);
      
      if (n > ep->common.buffer_size) {
	n = ep->received = ep->common.buffer_size;
	ret = -ENOMEM;
	TRACE_D12("\tEP%d: *** Rx Buffer too small. Data Lost ***\n", endp);
      }
      else
	ret = ep->received = n;
      
      memcpy(ep->common.buffer, tmp_buf, n);
      buf_remaining = ep->common.buffer_size - n;
    }
    
    TRACE_D12("\tEP%d: Rx Complete. Buffer (nearly) full. [%d]\n", 
	      endp, buf_remaining);
    usbs_d12_ep_rx_complete(ep, ret);
    return;
  }
  
  // ----- Read the data from the chip -----
  
  if (endp == D12_MAIN_ENDP)
    n = d12_read_main_endp_buf(D12_BASE_ADDR, 
			       ep->common.buffer + ep->received, &done);
  else {
    n = d12_read_endp_buf(D12_BASE_ADDR, RX_ENDP_INDEX[endp],
			  ep->common.buffer + ep->received);
    done = (n < RX_ENDP_SIZE[endp]);
  }
  
  ep->received += n;
  buf_remaining = ep->common.buffer_size - ep->received;
  
  done = done || (buf_remaining < ep_size);
  
  TRACE_D12("EP%d: Received %d bytes.\n", endp, n);
  TRACE_BUF("\t", ep->common.buffer + ep->received-n, n);
  
  // ----- If we're done, complete the receive -----
  
  if (done) {
    TRACE_D12("\tEP%d Rx Complete (%d)  %p\n", endp, 
	      ep->received, ep->common.complete_fn);
    usbs_d12_ep_rx_complete(ep, ep->received);
  }
}

// --------------------------------------------------------------------------
// Stalls/unstalls the specified endpoint.

static void 
usbs_d12_stall_rx_ep(rx_endpoint *ep, cyg_bool halt)
{
  ep->common.halted = halt;
  d12_stall_endp(D12_BASE_ADDR, RX_ENDP_INDEX[ep->endp], halt);
}

// --------------------------------------------------------------------------
// Handler for an Rx endpoint full interrupt. It clears the interrupt on the
// D12 by reading the endpoint's status register then calls the routine to
// read the data into the buffer.
//
// Called from the DSR context only.
//

static void 
usbs_d12_ep_rx_intr(rx_endpoint *ep)
{
  d12_read_last_trans_status(D12_BASE_ADDR, RX_ENDP_INDEX[ep->endp]);
  usbs_d12_ep_rx(ep);
}

#endif

// --------------------------------------------------------------------------
// Common Tx Endpoint 1 & 2
// --------------------------------------------------------------------------

#if defined(_TX_EP1) || defined(_TX_EP2)

// Clears out the endpoint data structure before/after a tx is complete.

static void usbs_d12_clear_tx_ep(tx_endpoint *ep)
{
  ep->common.buffer = 0;
  ep->common.buffer_size = 0;
  ep->common.complete_fn = 0;
  ep->common.complete_data = 0;
  
  ep->transmitted = 0;
  ep->tx_empty = false;
}

// --------------------------------------------------------------------------
// This is called when a transmit is completed. It resets the endpoint vars
// and calls the registered completion function, if any.
//
// CONTEXT:
//         Called from either the DSR or the app thread that started tx. 

static void usbs_d12_ep_tx_complete(tx_endpoint *ep, int result)
{
  completion_fn fn = ep->common.complete_fn;
  void *data = ep->common.complete_data;
  
  usbs_d12_clear_tx_ep(ep);
  
  if (fn)
    (*fn)(data, result);
}

// --------------------------------------------------------------------------
// The routine writes data to the chip and updates the endpoint's counters. 
// It gets called at the start of a transfer operation to prime the device
// and then gets called each time the chip finishes sending a packet to the
// host and is ready for more data. If the amount of data remaining is 
// smaller than can fit in the chip's endpoint buffer, then this is the last
// packet to send, so we call the completion function.
//
// CONTEXT:
//        Called from either the DSR or the app thread that started the tx
//        In either case, it's assumed the chip is locked.

static void 
usbs_d12_ep_tx(tx_endpoint *ep)
{
  int n, nRemaining;
  
  // ----- Already done. Intermittent interrupt, so get out -----
  
  if (!ep->common.buffer)
    return;
  
  // ----- See how many bytes remaining in buffer -----
  
  nRemaining = ep->common.buffer_size - ep->transmitted;
  
  TRACE_D12("EP%d: Tx %p, %d Done, %d Remaining\n", ep->endp, 
	    ep->common.buffer, ep->transmitted, nRemaining);
  
  // ----- If prev packet was last, signal that we're done -----
  
  if (nRemaining == 0 && !ep->tx_empty) {
    TRACE_D12("\tEP%d: Tx complete (%d)  %p\n", ep->endp, 
	      ep->transmitted, ep->common.complete_fn);
    usbs_d12_ep_tx_complete(ep, ep->transmitted);
    return;
  }
  
  // ----- Write the next packet to chip -----
  
  if (nRemaining < TX_ENDP_SIZE[ep->endp]) {
    n = nRemaining;
    ep->tx_empty = false;
  }
  else
    n = TX_ENDP_SIZE[ep->endp];
  
  TRACE_D12("EP%d: Writing %d bytes. %s\n", ep->endp, 
	    n, (n == 0) ? "DONE" : "");
  TRACE_BUF("\t", ep->common.buffer + ep->transmitted, n);
  
  d12_write_endp_buf(D12_BASE_ADDR, TX_ENDP_INDEX[ep->endp], 
		     ep->common.buffer + ep->transmitted, (uint8) n);
  
  ep->transmitted += n;
  
  // ----- If empty packet, complete now -----
  
  if (n == 0) {
    TRACE_D12("\tEP%d: Tx complete (%d)  %p\n", ep->endp, 
	      ep->transmitted, ep->common.complete_fn);
    usbs_d12_ep_tx_complete(ep, ep->transmitted);
    return;
  }
}

// --------------------------------------------------------------------------
// Stalls/unstalls the specified tx endpoint.

static void 
usbs_d12_stall_tx_ep(tx_endpoint *ep, cyg_bool halt)
{
  ep->common.halted = halt;
  d12_stall_endp(D12_BASE_ADDR, TX_ENDP_INDEX[ep->endp], halt);
}

// --------------------------------------------------------------------------
// Handler for when the chip's tx RAM for an endoint has just been emptied 
// (sent to the host) and the chip is ready for more data.
// We read the endpoint's last trans status register to clear the interrupt
// on the D12, then call the tx function to send the next packet or 
// complete the transfer.

static void 
usbs_d12_ep_tx_intr(tx_endpoint *ep)
{
  d12_read_last_trans_status(D12_BASE_ADDR, TX_ENDP_INDEX[ep->endp]);
  usbs_d12_ep_tx(ep);
}

#endif // defined(_TX_EP1) || defined(_TX_EP2)

// --------------------------------------------------------------------------
// Application Program Interface (API)
// --------------------------------------------------------------------------

#if defined(_RX_EP1) || defined(_RX_EP2)
// Starts a receive operation on the specified endpoint. If the buffer size
// is zero the completion function is called immediately. The routine checks
// if tehre is data in the chip's endpoint buffer, and if so it will call
// ep_rx() to start reading the data out of the chip.
//
// If the endpoint is currently stalled, a read size of zero can be used to 
// block the calling thread until the stall is cleared. If the read size is
// non-zero and the endpoint is stalled the completion function is called
// immediately with an error result.

static void 
usbs_d12_api_start_rx_ep(usbs_rx_endpoint *ep)
{
  rx_endpoint *epx = (rx_endpoint *) ep;
  
  if (ep->halted) {
    if (ep->buffer_size != 0)
      usbs_d12_ep_rx_complete(epx, -EAGAIN);
  }
  else if (ep->buffer_size == 0) {
    usbs_d12_ep_rx_complete(epx, 0);
  }
  else {
    TRACE_D12("EP%d: Starting Rx, %p, %d\n", epx->endp, ep->buffer,
	      ep->buffer_size);
    usbs_d12_lock();
    
    epx->received = 0;
    if (d12_data_available(D12_BASE_ADDR, RX_ENDP_INDEX[epx->endp]))
      usbs_d12_ep_rx(epx);
    
    usbs_d12_unlock();
  }
}

// --------------------------------------------------------------------------
// Halts/unhalts one of the generic rx (OUT) endpoints.
//

static void usbs_d12_api_stall_rx_ep(usbs_rx_endpoint *ep, cyg_bool halt)
{
  usbs_d12_lock();
  usbs_d12_stall_rx_ep((rx_endpoint*) ep, halt);
  usbs_d12_unlock();
}

#endif // defined(_RX_EP1) || defined(_RX_EP2)

// --------------------------------------------------------------------------
// Tx API
// --------------------------------------------------------------------------

#if defined(_TX_EP1) || defined(_TX_EP2)

// This starts a transmit on one of the data endpoints. If the endpoint is
// stalled a buffer size of zero can be used to block until the stall is
// cleared. Any other size on a stalled endpoint will result in an error
// callback immediately. The first packet is sent to the chip immediately,
// in the application context. If the chip's buffer can contain the whole
// transfer, the completion function will be called immediately, again,
// still in the application context.
//
// If an empty packet is requested we send one from here and call the 
// completion function. This should not cause an intr on the D12.
//
// CONTEXT:
//        Called from an application thread

static void usbs_d12_api_start_tx_ep(usbs_tx_endpoint *ep)
{
  tx_endpoint *epx = (tx_endpoint*) ep;
  
  if (ep->halted) {
    if (ep->buffer_size != 0) 
      usbs_d12_ep_tx_complete(epx, -EAGAIN);
  }
  else if (ep->buffer_size == 0) {
    usbs_d12_lock();
    
    d12_write_endp_buf(D12_BASE_ADDR, TX_ENDP_INDEX[epx->endp], 0, 0);
    usbs_d12_ep_tx_complete(epx, 0);
    
    usbs_d12_unlock();
  }
  else {
    TRACE_D12("EP%d: Starting Tx, %p, %d\n", epx->endp, ep->buffer,
	      ep->buffer_size);
    usbs_d12_lock();
    
    epx->tx_empty = true;
    epx->transmitted = 0;
    usbs_d12_ep_tx(epx);
    
    usbs_d12_unlock();
  }
}

// --------------------------------------------------------------------------
// Halts/unhalts one of the generic endpoints.

static void 
usbs_d12_api_stall_tx_ep(usbs_tx_endpoint *ep, cyg_bool halt)
{
  usbs_d12_lock();
  usbs_d12_stall_tx_ep((tx_endpoint*) ep, halt);
  usbs_d12_unlock();
}

#endif // defined(_TX_ENDP1) || defined(_TX_EP2)

// --------------------------------------------------------------------------
// DSR
// --------------------------------------------------------------------------

// The DSR for the D12 chip. This is normally called in the DSR context when
// the D12 has raised its interrupt flag indicating that it needs to be 
// serviced. The interrupt register contains bit flags that are OR'ed togther
// indicating what items need to be serviced. There are flags for the 
// following:
//              - The endpoints (one bit for each)
//              - Bus Reset
//              - Suspend Change
//              - DMA (terminal count)
//
// Care must be taken in that the D12's interrupt output is level-sensitive
// (in that the interrupt sources are OR'ed together and not all cleared 
// atomically in a single operation). Platforms (such as the PC) may be 
// expecting edge-triggered interrupts, so we must work around that.
// So, we loop on the interrupt register. Even though, in each loop, we
// perform all of the required operations to clear the interrupts, a new
// one may have arrived before we finished clearing the previous ones.
// So we read the intr reg again. Once the intr reg gives a zero reading
// we know that the D12 has dropped its IRQ line.
//
// Note, if we're configured to use a thread, this routine is called from
// within a thread context (not a DSR context).
//

static void 
usbs_d12_dsr(cyg_vector_t vector, cyg_ucount32 count, 
                                                 cyg_addrword_t data)
{
  uint16  status;
  bool    suspended;
  
  CYG_ASSERT(vector == CYGNUM_DEVS_USB_D12_INT,
	     "DSR should only be invoked for D12 interrupts");
  
  while ((status = d12_read_intr_reg(D12_BASE_ADDR)) != 0) {
    TRACE_D12("Intr Status: 0x%04X\n", (unsigned) status);
    
    if (status & D12_INTR_BUS_RESET) {
      TRACE_D12("\n>>> Bus Reset <<<\n");
      usbs_d12_reset();
    }
    else {
      
      // ----- Suspend Change -----
      
      suspended = (bool) (ep0.common.state & USBS_STATE_SUSPENDED);
      
      if (status & D12_INTR_SUSPEND_CHANGE) {
	if (!suspended && (status & ~D12_INTR_SUSPEND_CHANGE) == 0)
	  usbs_d12_suspend(true);
      }
      else if (suspended)
	usbs_d12_suspend(false);
      
      // ----- Bulk Endpoints -----
      
#ifdef _TX_EP2
      if (status & D12_INTR_TX_ENDP2)
	usbs_d12_ep_tx_intr(&tx_ep2);
#endif
      
#ifdef _RX_EP2
      if (status & D12_INTR_RX_ENDP2)
	usbs_d12_ep_rx_intr(&rx_ep2);
#endif
      
      // ----- Interrupt Endpoints -----
      
#ifdef _TX_EP1
      if (status & D12_INTR_TX_ENDP1)
	usbs_d12_ep_tx_intr(&tx_ep1);
#endif
      
#ifdef _RX_EP1
      if (status & D12_INTR_RX_ENDP1)
	usbs_d12_ep_rx_intr(&rx_ep1);
#endif
      
      // ----- Control Endpoint -----
      
      if (status & D12_INTR_TX_CTRL_ENDP)
	usbs_d12_ep0_tx_intr();
      
      if (status & D12_INTR_RX_CTRL_ENDP)
	usbs_d12_ep0_rx_intr();
    }
  }
  
  cyg_drv_interrupt_unmask(vector);
}

// --------------------------------------------------------------------------
// Interrupt
// --------------------------------------------------------------------------

// Here, the ISR does nothing but schedule the DSR to run. The ISR's/DSR's
// are serialized. The CPU won't process another ISR until after the DSR
// completes.

static uint32 
usbs_d12_isr(cyg_vector_t vector, cyg_addrword_t data)
{
  CYG_ASSERT(CYGNUM_DEVS_USB_D12_INT == vector, 
	     "usbs_isr: Incorrect interrupt");
  
  // Prevent another interrupt until DSR completes
  cyg_drv_interrupt_mask(vector);
  cyg_drv_interrupt_acknowledge(vector);
  
  return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR;
}

// --------------------------------------------------------------------------
// Polling
// --------------------------------------------------------------------------

static void 
usbs_d12_poll(usbs_control_endpoint *endp)
{
  CYG_ASSERT(endp == &ep0.common, "usbs_poll: wrong endpoint");
  
  usbs_d12_lock();
  usbs_d12_dsr(CYGNUM_DEVS_USB_D12_INT, 0, 0);
  usbs_d12_unlock();
}

// --------------------------------------------------------------------------
// Thread Processing
// --------------------------------------------------------------------------

// The user can opt to configure the driver to service the D12 using a high
// priority thread. The thread's priority MUST be greater than the priority
// of any application thread making calls into the driver.
// When we use a thread, the DSR simply signals a semaphore tio wake the
// thread up. The thread, in turn, calls the the routine to service the D12,
// now in a thread context. This allows for greater debug options, including
// tracing.

#ifdef CYGPKG_DEVS_USB_D12_THREAD

static byte usbs_d12_thread_stack[CYGNUM_DEVS_USB_D12_THREAD_STACK_SIZE];
static cyg_thread   usbs_d12_thread;
static cyg_handle_t usbs_d12_thread_handle;
static cyg_sem_t    usbs_d12_sem;

static void 
usbs_d12_thread_dsr(cyg_vector_t vector, cyg_ucount32 count,
		    cyg_addrword_t data)
{
  cyg_semaphore_post(&usbs_d12_sem);
  
  CYG_UNUSED_PARAM(cyg_vector_t, vector);
  CYG_UNUSED_PARAM(cyg_ucount32, count);
  CYG_UNUSED_PARAM(cyg_addrword_t, data);
}


static void 
usbs_d12_thread_fn(cyg_addrword_t param)
{
  while (1) {
    cyg_semaphore_wait(&usbs_d12_sem);
    usbs_d12_poll(&ep0.common);
  }
  
  CYG_UNUSED_PARAM(cyg_addrword_t, param);
}


static void 
usbs_d12_thread_init(void)
{
  cyg_semaphore_init(&usbs_d12_sem, 0);
  
  cyg_thread_create(CYGNUM_DEVS_USB_D12_THREAD_PRIORITY,
		    &usbs_d12_thread_fn, 0, "D12 USB Driver Thread",
		    usbs_d12_thread_stack, 
		    CYGNUM_DEVS_USB_D12_THREAD_STACK_SIZE,
		    &usbs_d12_thread_handle, &usbs_d12_thread);
  cyg_thread_resume(usbs_d12_thread_handle);
}

#endif  // CYGPKG_DEVS_USB_D12_THREAD

// --------------------------------------------------------------------------
// Start/Reset
// --------------------------------------------------------------------------

// Chip initialization and handler for a USB Bus Reset. This gets called at
// system startup and after a USB Bus Reset. It puts the chip into the
// default state, with USB Address 0, connected to the bus (i.e. 
// "SoftConnect" asserted). Interrupts to the main endpoint  are turned on
// via the DMA register. 

static void 
usbs_d12_reset(void)
{
  int old_state = ep0.common.state;
  ep0.common.state = USBS_STATE_DEFAULT;
  
  if (ep0.common.state_change_fn) {
    (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data,
				  USBS_STATE_CHANGE_RESET, old_state);
  }
  
  d12_set_addr_enable(D12_BASE_ADDR, 0, true);
  d12_set_endp_enable(D12_BASE_ADDR, true);
  d12_set_dma(D12_BASE_ADDR, D12_DMA_MAIN_ENDP_INTR_ENABLE);
  d12_set_mode(D12_BASE_ADDR, D12_MODE_CFG_DFLT | D12_MODE_CFG_SOFT_CONNECT,
	       D12_MODE_CLK_DFLT);
  
  // If any endpoints were going, signal the end of transfers
  
#if defined(_TX_EP2)
  usbs_d12_ep_tx_complete(&tx_ep2, -EPIPE);
#endif
  
#if defined(_RX_EP2)
  usbs_d12_ep_rx_complete(&rx_ep2, -EPIPE);
#endif
  
#if defined(_TX_EP1)
  usbs_d12_ep_tx_complete(&tx_ep1, -EPIPE);
#endif
  
#if defined(_RX_EP1)
  usbs_d12_ep_rx_complete(&rx_ep1, -EPIPE);
#endif
}

// --------------------------------------------------------------------------
// The start function is called indirectly by the application when 
// initialization is complete. By this time, the enumeration data has been
// assigned to the control endpoint and we're ready to connect to the host.
// Within the reset function the D12's SoftConnect line is asserted which
// allows the host (hub) to see us on the USB bus. If connected, the host 
// should start the enumeration process.
//

static void usbs_d12_start(usbs_control_endpoint *endpoint)
{
#if defined(_TRACE) && !defined(_TRACE_STDOUT)
  TRACE_OPEN(TRACE_SINK);
#endif
  
  CYG_ASSERT(endpoint == &ep0.common, "ep0 start: wrong endpoint");
  TRACE_D12("USBS D12: Starting.\n");
  
  d12_clear_all_intr(D12_BASE_ADDR);
  usbs_d12_reset();
}       

// --------------------------------------------------------------------------
// Initialization
// --------------------------------------------------------------------------

// This routine is called early in the program's startup, possibly before
// main (from within a C++ object initialization). We want to put this chip
// and driver in a neutral, but ready, state until the application gets 
// control, initializes itself and calls the usb start function.
//
// The D12 has a "Soft Connect" feature to tristate the USB bus, making it
// appear that the USB device is not connected to the bus. We initially
// keep seperated from the bus to allow for initialization.

void 
usbs_d12_init(void)
{
  cyg_DSR_t *pdsr;
  
  d12_set_mode(D12_BASE_ADDR, D12_MODE_CFG_DFLT & ~D12_MODE_CFG_SOFT_CONNECT, 
	       D12_MODE_CLK_DFLT);
  
  d12_set_addr_enable(D12_BASE_ADDR, 0, false);
  d12_set_endp_enable(D12_BASE_ADDR, false);
  
  // ----- Clear the endpoints -----
  
#if defined(_TX_EP2)
  usbs_d12_clear_tx_ep(&tx_ep2);
#endif
  
#if defined(_RX_EP2)
  usbs_d12_clear_rx_ep(&rx_ep2);
#endif
  
#if defined(_TX_EP1)
  usbs_d12_clear_tx_ep(&tx_ep1);
#endif
  
#if defined(_RX_EP1)
  usbs_d12_clear_rx_ep(&rx_ep1);
#endif
  
  // ----- Start the thread (if we're using it) -----
  
#ifdef CYGPKG_DEVS_USB_D12_THREAD
  usbs_d12_thread_init();
  pdsr = &usbs_d12_thread_dsr;
#else
  pdsr = &usbs_d12_dsr;
#endif
  
  // ----- Attach the ISR -----
  
  cyg_drv_interrupt_create(CYGNUM_DEVS_USB_D12_INT, 
			   0, 0, &usbs_d12_isr, pdsr,
			   &usbs_d12_intr_handle, &usbs_d12_intr_data);
  
  cyg_drv_interrupt_attach(usbs_d12_intr_handle);
  cyg_drv_interrupt_unmask(CYGNUM_DEVS_USB_D12_INT);
}

// ----------------------------------------------------------------------------
// Testing support.

usbs_testing_endpoint usbs_testing_endpoints[] = {
  {
  endpoint_type       : USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL, 
  endpoint_number     : 0,
  endpoint_direction  : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN,
  endpoint            : (void*) &ep0.common,
#ifdef CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY
  devtab_entry        : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "0c",
#else        
  devtab_entry        : (const char*) 0,
#endif        
  min_size            : 1,
  max_size            : CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE,
  max_in_padding      : 0,
  alignment           : 0
  },
  
  /*
#ifdef _TX_EP1
  {   
  endpoint_type       : USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT,
  endpoint_number     : 1,
  endpoint_direction  : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN,
  endpoint            : (void*) &tx_ep1.common,
# ifdef CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY
  devtab_entry        : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "1w",
# else        
  devtab_entry        : (const char*) 0,
# endif        
  min_size            : 0,
  max_size            : 0x0FFFF,      // Driver limitation, only a single 
                                      // buffer descriptor is used
  max_in_padding      : 0,
  alignment           : HAL_DCACHE_LINE_SIZE
  },
#endif
  
#ifdef _RX_EP1
  {
  endpoint_type       : USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT,
  endpoint_number     : 1,
  endpoint_direction  : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT,
  endpoint            : (void*) &rx_ep1.common,
# ifdef CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY
  devtab_entry        : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "1r",
# else        
  devtab_entry        : (const char*) 0,
# endif        
  min_size            : 1,
  max_size            : 0x0FFFF,      // Driver limitation
  max_in_padding      : 0,
  alignment           : HAL_DCACHE_LINE_SIZE
  },
#endif
  */
  
#ifdef _TX_EP2
  {
  endpoint_type       : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK,
  endpoint_number     : 2,
  endpoint_direction  : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN,
  endpoint            : (void*) &tx_ep2.common,
# ifdef CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY
  devtab_entry        : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "2w",
# else        
  devtab_entry        : (const char*) 0,
# endif        
  min_size            : 0,
  max_size            : 0x1000,   // 4k for testing
  max_in_padding      : 0,
  alignment           : HAL_DCACHE_LINE_SIZE
  },
#endif
  
#ifdef _RX_EP2
  {
  endpoint_type       : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK,
  endpoint_number     : 2,
  endpoint_direction  : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT,
  endpoint            : (void*) &rx_ep2.common,
# ifdef CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY
  devtab_entry        : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "2r",
# else        
  devtab_entry        : (const char*) 0,
# endif        
  min_size            : 1,
  max_size            : 0x1000,           // 4k for testing
  max_in_padding      : 0,
  alignment           : HAL_DCACHE_LINE_SIZE
  },
#endif
  
  USBS_TESTING_ENDPOINTS_TERMINATOR
};

