/*
 USB setup protocol handling and vendor requests
 
 Copyright (c) 2008 Dimtel, Inc., All Rights Reserved

 $Id: usb.c,v 1.2 2013/06/06 04:35:11 dim Exp $
 $Date: 2013/06/06 04:35:11 $
 $Author: dim $
 $Revision: 1.2 $
 $Log: usb.c,v $
 Revision 1.2  2013/06/06 04:35:11  dim
 Added EP8 OUT to interface 0 for fast JTAG block loading

 Revision 1.1  2012/11/11 01:24:32  dim
 Moved from BxB, adapted to compile for both LLRF4 and IGP12

 Revision 1.1  2011/12/27 19:31:47  dim
 Updated to remove deprecated constructs (at, sfr, xdata, sbit)


*/

#include "fx2regs.h"
#include "usb.h"
#include "vendor.h"
#include "usb_descriptors.h"
#include "hwdef.h"
#include "../include/usb_defs.h"
#include "../include/board_ids.h"

extern volatile __bit SUDAV_service;

uchar usb_config_sel = 0;
uchar usb_alt_sel    = 0;

__xdata usb_device *descr_config;
__xdata usb_device *descr_other;

extern __xdata const usb_device descr_device;
extern __xdata const usb_device descr_qualifier;
extern __xdata const usb_device hs_descr_config;
extern __xdata const usb_device fs_descr_config;

/* Array of pointers to string descriptors */
extern __xdata usb_string * __xdata string_ptrs[];

void usb_update_descriptors(void)
{
  if (USBCS & bmHSM) /* High-speed setup */
  {
    descr_config    = &hs_descr_config;
    descr_other     = &fs_descr_config;
  }
  else               /* Full-speed setup */
  {
    descr_config    = &fs_descr_config;
    descr_other     = &hs_descr_config;
  }
}

/* Convert endpoint to control/status register pointer */
__xdata volatile uchar *ep_to_cs(uchar ep)
{
  if (ep == 1)
    return EP1OUTCS;
  
  if (ep == 0x81)
    return EP1INCS;
    
  ep = ep & USB_ENDPOINT_NUMBER_MASK;
  
  if (ep == 0)
    return EP0CS;
  
  return EP2CS + (ep >> 1);
}

/* Check if a valid endpoint is supplied. For FX2LP the only endpoints
   are: 0, 1, 2, 4, 6, 8. Returns 1 if valid, 0 otherwise.
*/
uchar ep_num_check(uchar ep)
{
  /* Ignore direction */
  ep = ep & USB_ENDPOINT_NUMBER_MASK;
  
  if (ep == 1) return 1;
  
  if (ep > 8) return 0;
  
  return (ep % 2 == 0);
}

void usb_setup_handler(void)
{
  setupdata *setup = (setupdata *)SETUPDAT;
  
  /* Reset service bit */
  SUDAV_service = 0;
  
  switch (setup->bmRequestType & USB_TYPE_MASK)
  {
    case USB_TYPE_STANDARD:
      /* Standard requests */
      if ((setup->bmRequestType & USB_DIR_IN) == USB_DIR_IN)
      {
        /* Handle the IN requests */
        switch (setup->bmRequest)
        {
          case USB_REQ_GET_STATUS:
            switch (setup->bmRequestType & USB_RECIP_MASK)
            {
              /* TRM: 2.3.1, 2-8 */
              case USB_RECIP_DEVICE:
                EP0BUF[0] = 1 << USB_DEVICE_SELF_POWERED;
                EP0BUF[1] = 0;
                EP0BCH    = 0;
                EP0BCL    = 2;
                break;
              /* TRM: 2.3.1, 2-9 */
              case USB_RECIP_INTERFACE:
                EP0BUF[0] = 0;
                EP0BUF[1] = 0;
                EP0BCH    = 0;
                EP0BCL    = 2;
                break;
              /* TRM: 2.3.1, 2-8 */
              case USB_RECIP_ENDPOINT:
                if (ep_num_check(setup->wIndexL))
                {
                  EP0BUF[0] = *ep_to_cs(setup->wIndexL) & bmEPSTALL;
                  EP0BUF[1] = 0;
                  EP0BCH    = 0;
                  EP0BCL    = 2;
                }
                else
                  /* Indicate error - invalid endpoint */
                  EP0CS |= bmEPSTALL;
                break;
              default:
                EP0CS |= bmEPSTALL;
            }
            break;
          case USB_REQ_GET_DESCRIPTOR:
            switch (setup->wValueH)
            {
              /* TRM: 2.3.4.1, 2-14 */
              case USB_DT_DEVICE:
                SUDPTRH = ((ushort)&descr_device) >> 8;
                SUDPTRL = ((ushort)&descr_device) & 0xff;
                break;
              /* TRM: 2.3.4.2, 2-15 */
              case USB_DT_DEVICE_QUALIFIER:
                SUDPTRH = ((ushort)&descr_qualifier) >> 8;
                SUDPTRL = ((ushort)&descr_qualifier) & 0xff;
                break;
              /* TRM: 2.3.4.3, 2-16 */
              case USB_DT_CONFIG:
                if (setup->wValueL != 0)
                  EP0CS |= bmEPSTALL;
                else
                {
                  SUDPTRH = (ushort)descr_config >> 8;
                  SUDPTRL = (ushort)descr_config & 0xff;
                }
                break;
              /* TRM: 2.3.4.4, 2-16 */
              case USB_DT_STRING:
                if (setup->wValueL >= N_DESC_STRINGS)
                  EP0CS |= bmEPSTALL;
                else
                {
                  __xdata usb_string *ptr = string_ptrs[setup->wValueL];
                  SUDPTRH = (ushort)ptr >> 8;
                  SUDPTRL = (ushort)ptr & 0xff;
                }
                break;
              /* TRM: 2.3.4.5, 2-17 */
              case USB_DT_OTHER_SPEED_CONFIG:
                if (setup->wValueL != 0)
                  EP0CS |= bmEPSTALL;
                else
                {
                  SUDPTRH = (ushort)descr_other >> 8;
                  SUDPTRL = (ushort)descr_other & 0xff;
                }
                break;
              default:
                EP0CS |= bmEPSTALL;
            }
            break;
          /* TRM: 2.3.6, 2-20 */
          case USB_REQ_GET_CONFIGURATION:
            EP0BUF[0] = usb_config_sel;
            EP0BCH    = 0;
            EP0BCL    = 1;
            break;
          /* TRM: 2.3.8, 2-22 */
          case USB_REQ_GET_INTERFACE:
            EP0BUF[0] = usb_alt_sel;
            EP0BCH    = 0;
            EP0BCL    = 1;
            break;
          case USB_REQ_SYNCH_FRAME:
          default:
            EP0CS |= bmEPSTALL; /* Not implemented */
            break;
        }
      }
      else
      { /* OUT requests are handled here */
        switch (setup->bmRequest)
        {
          /* TRM: 2.3.3, 2-12 */
          case USB_REQ_CLEAR_FEATURE:
            switch (setup->bmRequestType & USB_RECIP_MASK)
            {
              case USB_RECIP_DEVICE:
                /* The only device-level feature - clear remote 
                   wakeup bit - is not implemented */
                EP0CS |= bmEPSTALL;
                break;
              case USB_RECIP_ENDPOINT:
                if (setup->wValueL == 0 && ep_num_check(setup->wIndexL))
                {
                  /* Clear endpoint stall */
                  * ep_to_cs(setup->wIndexL) &= ~bmEPSTALL;
                  reset_toggle(setup->wIndexL);
                }
                else
                  EP0CS |= bmEPSTALL;
                break;
              default:
                EP0CS |= bmEPSTALL;
            }
            break;
          /* TRM: 2.3.2, 2-10 */
          case USB_REQ_SET_FEATURE:
            switch (setup->bmRequestType & USB_RECIP_MASK)
            {
              case USB_RECIP_DEVICE:
                if (setup->wValueL == 1) /* Set remote wakeup - not valid */
                  EP0CS |= bmEPSTALL;
                break;
              case USB_RECIP_ENDPOINT:
                if (setup->wValueL == 0 && ep_num_check(setup->wIndexL))
                  /* Endpoint stall */
                  * ep_to_cs(setup->wIndexL) |= bmEPSTALL;
                else
                  EP0CS |= bmEPSTALL;
                break;
              default:
                EP0CS |= bmEPSTALL;
            }
            break;
          /* TRM: 2.3.5.1, 2-20 */
          case USB_REQ_SET_CONFIGURATION:
            usb_config_sel = setup->wValueL;
            if (usb_alt_sel == 0) /* Only EP0 (IN/OUT) and EP8 (OUT) */
            {
              reset_toggle(0);
              reset_toggle(8);
              reset_toggle(USB_ENDPOINT_DIR_MASK);
            }
            else
            {
              /* Reset toggles for endpoints 0x02 and 0x86 */
              reset_toggle(2);
              reset_toggle(USB_ENDPOINT_DIR_MASK | 6);
            }
            break;
          /* TRM: 2.3.7, 2-21 */
          case USB_REQ_SET_INTERFACE:
            usb_alt_sel = setup->wValueL;
            if (usb_alt_sel == 0) /* EP0 (IN/OUT) and EP8 (OUT) */
            {
              reset_toggle(0);
              reset_toggle(8);
              reset_toggle(USB_ENDPOINT_DIR_MASK);
            }
            else
            {
              /* Reset toggles for endpoints 0x02 and 0x86 */
              reset_toggle(2);
              reset_toggle(USB_ENDPOINT_DIR_MASK | 6);
            }
            break;
          case USB_REQ_SET_ADDRESS:	/* Never happens */
          case USB_REQ_SET_DESCRIPTOR:	/* Not implemented */
          default:
            EP0CS |= bmEPSTALL;
        }
      }
      break;
    case USB_TYPE_CLASS:
    case USB_TYPE_RESERVED:
      EP0CS |= bmEPSTALL;	/* Drop on the floor, not handled */
      break;
    
    case USB_TYPE_VENDOR:
      /* JTAG/i2c/monitor handler */
      if (!vendor_cmd_handler())
        EP0CS |= bmEPSTALL;
      break;
  }

  /* Clear the HSNAK bit */
  EP0CS |= bmHSNAK;
}
