/*
  Copyright (c) 2015 Arduino LLC.  All right reserved.
  Copyright (c) 2015 Atmel Corporation/Thibaut VIARD.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library 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 Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <stdint.h>
#include <string.h>
#include "board_definitions.h"
#include "sam_ba_usb.h"
#include "board_driver_usb.h"
#include "sam_ba_cdc.h"

#ifndef USB_CURRENT_MA
// default USB current, report using 100mA, enough for a bootloader
#define USB_CURRENT_MA 100
#endif

/* This data array will be copied into SRAM as its length is inferior to 64 bytes,
 * and so can stay in flash.
 */
static __attribute__((__aligned__(4)))
const char devDescriptor[] =
{
  /* Device descriptor */
  0x12,   // bLength
  0x01,   // bDescriptorType
  0x00,   // bcdUSB L
  0x02,   // bcdUSB H
  0x02,   // bDeviceClass:    CDC class code
  0x00,   // bDeviceSubclass: CDC class sub code
  0x00,   // bDeviceProtocol: CDC Device protocol
  0x40,   // bMaxPacketSize0
  USB_VID_LOW,   // idVendor L
  USB_VID_HIGH,   // idVendor H
  USB_PID_LOW,   // idProduct L
  USB_PID_HIGH,  // idProduct H
  0x00,   // bcdDevice L, here matching SAM-BA version
  0x02,   // bcdDevice H
  STRING_INDEX_MANUFACTURER,   // iManufacturer
  STRING_INDEX_PRODUCT,        // iProduct
  0x00,   // SerialNumber, should be based on product unique ID
  0x01    // bNumConfigs
};

/* This data array will be consumed directly by USB_Write() and must be in SRAM.
 * We cannot send data from product internal flash.
 */
static __attribute__((__aligned__(4)))
char cfgDescriptor[] =
{
  /* ============== CONFIGURATION 1 =========== */
  /* Configuration 1 descriptor */
  0x09,   // CbLength
  0x02,   // CbDescriptorType
  0x43,   // CwTotalLength 2 EP + Control
  0x00,
  0x02,   // CbNumInterfaces
  0x01,   // CbConfigurationValue
  0x00,   // CiConfiguration
  0x80,   // CbmAttributes Bus powered without remote wakeup: 0x80, Self powered without remote wakeup: 0xc0
  (USB_CURRENT_MA / 2),   // CMaxPower

  /* Communication Class Interface Descriptor Requirement */
  0x09, // bLength
  0x04, // bDescriptorType
  0x00, // bInterfaceNumber
  0x00, // bAlternateSetting
  0x01, // bNumEndpoints
  0x02, // bInterfaceClass
  0x02, // bInterfaceSubclass
  0x00, // bInterfaceProtocol
  0x00, // iInterface

  /* Header Functional Descriptor */
  0x05, // bFunction Length
  0x24, // bDescriptor type: CS_INTERFACE
  0x00, // bDescriptor subtype: Header Func Desc
  0x10, // bcdCDC:1.1
  0x01,

  /* ACM Functional Descriptor */
  0x04, // bFunctionLength
  0x24, // bDescriptor Type: CS_INTERFACE
  0x02, // bDescriptor Subtype: ACM Func Desc
  0x00, // bmCapabilities

  /* Union Functional Descriptor */
  0x05, // bFunctionLength
  0x24, // bDescriptorType: CS_INTERFACE
  0x06, // bDescriptor Subtype: Union Func Desc
  0x00, // bMasterInterface: Communication Class Interface
  0x01, // bSlaveInterface0: Data Class Interface

  /* Call Management Functional Descriptor */
  0x05, // bFunctionLength
  0x24, // bDescriptor Type: CS_INTERFACE
  0x01, // bDescriptor Subtype: Call Management Func Desc
  0x00, // bmCapabilities: D1 + D0
  0x01, // bDataInterface: Data Class Interface 1

  /* Endpoint 1 descriptor */
  0x07,   // bLength
  0x05,   // bDescriptorType
  0x83,   // bEndpointAddress, Endpoint 03 - IN
  0x03,   // bmAttributes      INT
  0x08,   // wMaxPacketSize
  0x00,
  0xFF,   // bInterval

  /* Data Class Interface Descriptor Requirement */
  0x09, // bLength
  0x04, // bDescriptorType
  0x01, // bInterfaceNumber
  0x00, // bAlternateSetting
  0x02, // bNumEndpoints
  0x0A, // bInterfaceClass
  0x00, // bInterfaceSubclass
  0x00, // bInterfaceProtocol
  0x00, // iInterface

  /* First alternate setting */
  /* Endpoint 1 descriptor */
  0x07,   // bLength
  0x05,   // bDescriptorType
  0x81,   // bEndpointAddress, Endpoint 01 - IN
  0x02,   // bmAttributes      BULK
  USB_EP_IN_SIZE,   // wMaxPacketSize
  0x00,
  0x00,   // bInterval

  /* Endpoint 2 descriptor */
  0x07,   // bLength
  0x05,   // bDescriptorType
  0x02,   // bEndpointAddress, Endpoint 02 - OUT
  0x02,   // bmAttributes      BULK
  USB_EP_OUT_SIZE,   // wMaxPacketSize
  0x00,
  0x00    // bInterval
};

#ifndef STRING_MANUFACTURER
#  define STRING_MANUFACTURER "Arduino LLC"
#endif

#ifndef STRING_PRODUCT
#  define STRING_PRODUCT "Arduino Zero"
#endif

USB_CDC sam_ba_cdc;

/*----------------------------------------------------------------------------
 * \brief This function is a callback invoked when a SETUP packet is received
 */
void sam_ba_usb_CDC_Enumerate(P_USB_CDC pCdc)
{
  Usb *pUsb = pCdc->pUsb;
  static volatile uint8_t bmRequestType, bRequest, dir;
  static volatile uint16_t wValue, wIndex, wLength, wStatus;

  /* Clear the Received Setup flag */
  pUsb->DEVICE.DeviceEndpoint[0].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_RXSTP;

  /* Read the USB request parameters */
  bmRequestType = udd_ep_out_cache_buffer[0][0];
  bRequest      = udd_ep_out_cache_buffer[0][1];
  wValue        = (udd_ep_out_cache_buffer[0][2] & 0xFF);
  wValue       |= (udd_ep_out_cache_buffer[0][3] << 8);
  wIndex        = (udd_ep_out_cache_buffer[0][4] & 0xFF);
  wIndex       |= (udd_ep_out_cache_buffer[0][5] << 8);
  wLength       = (udd_ep_out_cache_buffer[0][6] & 0xFF);
  wLength      |= (udd_ep_out_cache_buffer[0][7] << 8);

  /* Clear the Bank 0 ready flag on Control OUT */
  pUsb->DEVICE.DeviceEndpoint[0].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK0RDY;

  /* Handle supported standard device request Cf Table 9-3 in USB specification Rev 1.1 */
  switch ((bRequest << 8) | bmRequestType)
  {
    case STD_GET_DESCRIPTOR:
      if (wValue>>8 == STD_GET_DESCRIPTOR_DEVICE)
      {
        /* Return Device Descriptor */
        USB_Write(pCdc->pUsb, devDescriptor, SAM_BA_MIN(sizeof(devDescriptor), wLength), USB_EP_CTRL);
      }
      else if (wValue>>8 == STD_GET_DESCRIPTOR_CONFIGURATION)
      {
        /* Return Configuration Descriptor */
        USB_Write(pCdc->pUsb, cfgDescriptor, SAM_BA_MIN(sizeof(cfgDescriptor), wLength), USB_EP_CTRL);
      }
      else if (wValue>>8 == STD_GET_DESCRIPTOR_STRING)
      {
        switch ( wValue & 0xff )
        {
          case STRING_INDEX_LANGUAGES: {
            uint16_t STRING_LANGUAGE[2] = { (STD_GET_DESCRIPTOR_STRING<<8) | 4, 0x0409 };

            USB_Write(pCdc->pUsb, (const char*)STRING_LANGUAGE, SAM_BA_MIN(sizeof(STRING_LANGUAGE), wLength), USB_EP_CTRL);
          }
          break;

          case STRING_INDEX_MANUFACTURER:
            USB_SendString(pCdc->pUsb, STRING_MANUFACTURER, wLength );
          break;

          case STRING_INDEX_PRODUCT:
            USB_SendString(pCdc->pUsb, STRING_PRODUCT, wLength );
          break;
          default:
            /* Stall the request */
            USB_SendStall(pUsb, true);
          break;
        }
      }
      else
      {
        /* Stall the request */
        USB_SendStall(pUsb, true);
      }
    break;

    case STD_SET_ADDRESS:
      /* Send ZLP */
      USB_SendZlp(pUsb);
      /* Set device address to the newly received address from host */
      USB_SetAddress(pCdc->pUsb, wValue);
    break;

    case STD_SET_CONFIGURATION:
      /* Store configuration */
      pCdc->currentConfiguration = (uint8_t)wValue;

      /* Send ZLP */
      USB_SendZlp(pUsb);

      /* Configure the 3 needed endpoints */
      USB_Configure(pUsb);
    break;

    case STD_GET_CONFIGURATION:
      /* Return current configuration value */
      USB_Write(pCdc->pUsb, (char *) &(pCdc->currentConfiguration), sizeof(pCdc->currentConfiguration), USB_EP_CTRL);
    break;

    case STD_GET_STATUS_ZERO:
      wStatus = 0;
      USB_Write(pCdc->pUsb, (char *) &wStatus, sizeof(wStatus), USB_EP_CTRL);
    break;

    case STD_GET_STATUS_INTERFACE:
      wStatus = 0;
      USB_Write(pCdc->pUsb, (char *) &wStatus, sizeof(wStatus), USB_EP_CTRL);
    break;

    case STD_GET_STATUS_ENDPOINT:
      wStatus = 0;
      dir = wIndex & 80;
      wIndex &= 0x0F;
      if (wIndex <= 3)
      {
        if (dir)
        {
          wStatus = (pUsb->DEVICE.DeviceEndpoint[wIndex].EPSTATUS.reg & USB_DEVICE_EPSTATUSSET_STALLRQ1) ? 1 : 0;
        }
        else
        {
          wStatus = (pUsb->DEVICE.DeviceEndpoint[wIndex].EPSTATUS.reg & USB_DEVICE_EPSTATUSSET_STALLRQ0) ? 1 : 0;
        }
        /* Return current status of endpoint */
        USB_Write(pCdc->pUsb, (char *) &wStatus, sizeof(wStatus), USB_EP_CTRL);
      }
      else
      {
        /* Stall the request */
        USB_SendStall(pUsb, true);
      }
    break;

    case STD_SET_FEATURE_ZERO:
      /* Stall the request */
      USB_SendStall(pUsb, true);
    break;

    case STD_SET_FEATURE_INTERFACE:
      /* Send ZLP */
      USB_SendZlp(pUsb);
    break;

    case STD_SET_FEATURE_ENDPOINT:
      dir = wIndex & 0x80;
      wIndex &= 0x0F;
      if ((wValue == 0) && wIndex && (wIndex <= 3))
      {
        /* Set STALL request for the endpoint */
        if (dir)
        {
          pUsb->DEVICE.DeviceEndpoint[wIndex].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ1;
        }
        else
        {
          pUsb->DEVICE.DeviceEndpoint[wIndex].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ0;
        }

        /* Send ZLP */
        USB_SendZlp(pUsb);
      }
      else
      {
        /* Stall the request */
        USB_SendStall(pUsb, true);
      }
    break;

    case STD_SET_INTERFACE:
    case STD_CLEAR_FEATURE_ZERO:
      /* Stall the request */
      USB_SendStall(pUsb, true);
    break;

    case STD_CLEAR_FEATURE_INTERFACE:
      /* Send ZLP */
      USB_SendZlp(pUsb);
    break;

    case STD_CLEAR_FEATURE_ENDPOINT:
      dir = wIndex & 0x80;
      wIndex &= 0x0F;

      if ((wValue == 0) && wIndex && (wIndex <= 3))
      {
        if (dir)
        {
          if (pUsb->DEVICE.DeviceEndpoint[wIndex].EPSTATUS.bit.STALLRQ1)
          {
            // Remove stall request
            pUsb->DEVICE.DeviceEndpoint[wIndex].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ1;
            if (pUsb->DEVICE.DeviceEndpoint[wIndex].EPINTFLAG.bit.STALL1)
            {
              pUsb->DEVICE.DeviceEndpoint[wIndex].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL1;
              // The Stall has occurred, then reset data toggle
              pUsb->DEVICE.DeviceEndpoint[wIndex].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSSET_DTGLIN;
            }
          }
        }
        else
        {
          if (pUsb->DEVICE.DeviceEndpoint[wIndex].EPSTATUS.bit.STALLRQ0)
          {
            // Remove stall request
            pUsb->DEVICE.DeviceEndpoint[wIndex].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ0;
            if (pUsb->DEVICE.DeviceEndpoint[wIndex].EPINTFLAG.bit.STALL0)
            {
              pUsb->DEVICE.DeviceEndpoint[wIndex].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL0;
              // The Stall has occurred, then reset data toggle
              pUsb->DEVICE.DeviceEndpoint[wIndex].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSSET_DTGLOUT;
            }
          }
        }
        /* Send ZLP */
        USB_SendZlp(pUsb);
      }
      else
      {
        USB_SendStall(pUsb, true);
      }
    break;

    // handle CDC class requests
    case SET_LINE_CODING:
      /* Send ZLP */
      USB_SendZlp(pUsb);
    break;

    case GET_LINE_CODING:
      /* Send current line coding */
      USB_Write(pCdc->pUsb, (char *) &line_coding, SAM_BA_MIN(sizeof(usb_cdc_line_coding_t), wLength), USB_EP_CTRL);
    break;

    case SET_CONTROL_LINE_STATE:
      /* Store the current connection */
      pCdc->currentConnection = wValue;
      /* Send ZLP */
      USB_SendZlp(pUsb);
    break;

    default:
      /* Stall the request */
      USB_SendStall(pUsb, true);
    break;
  }
}

/*----------------------------------------------------------------------------
 * \brief
 */
P_USB_CDC usb_init(void)
{
  sam_ba_cdc.pUsb = USB;

  /* Initialize USB */
  USB_Init();
  /* Get the default CDC structure settings */
  USB_Open(&sam_ba_cdc, sam_ba_cdc.pUsb);

  return &sam_ba_cdc;
}

/*----------------------------------------------------------------------------
 * \brief Send a USB descriptor string.
 *
 * The input string is plain ASCII but is sent out as UTF-16 with the correct 2-byte prefix.
 */
uint32_t USB_SendString(Usb *pUsb, const char* ascii_string, uint8_t maxLength)
{
  uint8_t string_descriptor[255]; // Max USB-allowed string length
  uint16_t* unicode_string=(uint16_t*)(string_descriptor+2); // point on 3 bytes of descriptor
  int resulting_length;

  string_descriptor[0] = (strlen(ascii_string)<<1) + 2;
  string_descriptor[1] = STD_GET_DESCRIPTOR_STRING;

  for ( resulting_length = 1 ; *ascii_string && (resulting_length<maxLength>>1) ; resulting_length++ )
  {
    *unicode_string++ = (uint16_t)(*ascii_string++);
  }

  return USB_Write(pUsb, (const char*)string_descriptor, resulting_length<<1, USB_EP_CTRL);
}
