/*
 * This file contains all of functions, macros, definitions, variables,
 * datatypes, etc. that are required for usage with the CDC function
 * driver. This file should be included in projects that use the CDC
 * \function driver.
 *
 * The software supplied herewith by Microchip Technology Incorporated
 * (the 'Company') for its PICŪ Microcontroller is intended and
 * supplied to you, the Company's customer, for use solely and
 * exclusively on Microchip PIC Microcontroller products. The
 * software is owned by the Company and/or its supplier, and is
 * protected under applicable copyright laws. All rights are reserved.
 * Any use in violation of the foregoing restrictions may subject the
 * user to criminal sanctions under applicable laws, as well as to
 * civil liability for the breach of the terms and conditions of this license.
 *
 * THIS SOFTWARE IS PROVIDED IN AN 'AS IS' CONDITION. NO WARRANTIES,
 * WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED
 * TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. THE COMPANY SHALL NOT,
 * IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL OR
 * CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
 */
#include <machine/pic32mx.h>
#include <machine/usb_device.h>
#include <machine/usb_function_cdc.h>

unsigned cdc_trf_state;         // States are defined cdc.h
unsigned cdc_tx_len;            // total tx length

LINE_CODING cdc_line_coding;	// Buffer to store line coding information

static USB_HANDLE data_out;
static USB_HANDLE data_in;

static CONTROL_SIGNAL_BITMAP control_signal_bitmap;

static volatile unsigned char cdc_data_rx [CDC_DATA_OUT_EP_SIZE];
static volatile unsigned char cdc_data_tx [CDC_DATA_IN_EP_SIZE];

/*
 * SEND_ENCAPSULATED_COMMAND and GET_ENCAPSULATED_RESPONSE are required
 * requests according to the CDC specification.
 * However, it is not really being used here, therefore a dummy buffer is
 * used for conformance.
 */
#define DUMMY_LENGTH    0x08

static unsigned char dummy_encapsulated_cmd_response [DUMMY_LENGTH];

/*
 * This routine checks the setup data packet to see if it
 * knows how to handle it.
 */
void cdc_check_request()
{
    /*
     * If request recipient is not an interface then return
     */
    if (usb_setup_pkt.Recipient != RCPT_INTF)
        return;

    /*
     * If request type is not class-specific then return
     */
    if (usb_setup_pkt.RequestType != CLASS)
        return;

    /*
     * Interface ID must match interface numbers associated with
     * CDC class, else return
     */
    if (usb_setup_pkt.bIntfID != CDC_COMM_INTF_ID &&
        usb_setup_pkt.bIntfID != CDC_DATA_INTF_ID)
        return;

    switch (usb_setup_pkt.bRequest)
    {
    case SEND_ENCAPSULATED_COMMAND:
        // send the packet
        usb_in_pipe[0].pSrc.bRam = (unsigned char*) &dummy_encapsulated_cmd_response;
        usb_in_pipe[0].wCount = DUMMY_LENGTH;
        usb_in_pipe[0].info.bits.ctrl_trf_mem = USB_INPIPES_RAM;
        usb_in_pipe[0].info.bits.busy = 1;
        break;

    case GET_ENCAPSULATED_RESPONSE:
        // Populate dummy_encapsulated_cmd_response first.
        usb_in_pipe[0].pSrc.bRam = (unsigned char*) &dummy_encapsulated_cmd_response;
        usb_in_pipe[0].info.bits.busy = 1;
        break;

    case SET_LINE_CODING:
        usb_out_pipe[0].wCount = usb_setup_pkt.wLength;
        usb_out_pipe[0].pDst.bRam = (unsigned char*) &cdc_line_coding._byte[0];
        usb_out_pipe[0].pFunc = 0;
        usb_out_pipe[0].info.bits.busy = 1;
        break;

    case GET_LINE_CODING:
        usb_ep0_send_ram_ptr ((unsigned char*) &cdc_line_coding,
            LINE_CODING_LENGTH, USB_EP0_INCLUDE_ZERO);
        break;

    case SET_CONTROL_LINE_STATE:
        control_signal_bitmap._byte = (unsigned char)usb_setup_pkt.W_Value;
        CONFIGURE_RTS(control_signal_bitmap.CARRIER_CONTROL);
        CONFIGURE_DTR(control_signal_bitmap.DTE_PRESENT);
        usb_in_pipe[0].info.bits.busy = 1;
        break;
    }
}

/*
 * This function initializes the CDC function driver. This function sets
 * the default line coding (baud rate, bit parity, number of data bits,
 * and format). This function also enables the endpoints and prepares for
 * the first transfer from the host.
 *
 * This function should be called after the SET_CONFIGURATION command.
 * This is most simply done by calling this function from the
 * usbcb_init_ep() function.
 *
 * Usage:
 *     void usbcb_init_ep()
 *     {
 *         cdc_init_ep();
 *     }
 */
void cdc_init_ep()
{
    // Abstract line coding information
    cdc_line_coding.dwDTERate = 115200;	// baud rate
    cdc_line_coding.bCharFormat = 0;	// 1 stop bit
    cdc_line_coding.bParityType = 0;	// None
    cdc_line_coding.bDataBits = 8;	// 5,6,7,8, or 16

    cdc_trf_state = CDC_TX_READY;
    cdc_tx_len = 0;

    /*
     * Do not have to init Cnt of IN pipes here.
     * Reason:  Number of BYTEs to send to the host
     *          varies from one transaction to
     *          another. Cnt should equal the exact
     *          number of BYTEs to transmit for
     *          a given IN transaction.
     *          This number of BYTEs will only
     *          be known right before the data is
     *          sent.
     */
    usb_enable_endpoint (CDC_COMM_EP, USB_IN_ENABLED |
        USB_HANDSHAKE_ENABLED | USB_DISALLOW_SETUP);
    usb_enable_endpoint (CDC_DATA_EP, USB_IN_ENABLED | USB_OUT_ENABLED |
        USB_HANDSHAKE_ENABLED | USB_DISALLOW_SETUP);

    data_out = usb_rx_one_packet (CDC_DATA_EP,
        (unsigned char*) &cdc_data_rx, sizeof(cdc_data_rx));
    data_in = 0;
}

/*
 * Get received data.
 */
int cdc_consume (void (*func) (int))
{
    unsigned len;

    if (! data_out || usb_handle_busy (data_out))
        return 0;

    /*
     * Pass received data to user function.
     */
    len = usb_handle_get_length (data_out);
    if (func != 0) {
        unsigned n;
        for (n=0; n<len; n++)
            func (cdc_data_rx[n]);
    }

    /*
     * Prepare dual-ram buffer for next OUT transaction
     */
    data_out = usb_rx_one_packet (CDC_DATA_EP,
            (unsigned char*) &cdc_data_rx, sizeof(cdc_data_rx));
    return len;
}

/*
 * Send a symbol to the USB.
 * Return a number of free bytes in transmit buffer.
 *
 * Usage:
 *      if (cdc_is_tx_ready()) {
 *          do {
 *              space = cdc_putc (data[i++]);
 *          } while (space > 0);
 *      }
 * Conditions:
 *   cdc_is_tx_ready() must return TRUE. This indicates that the last
 *   transfer is complete and is ready to receive a new block of data.
 */
int cdc_putc (int c)
{
    if (cdc_trf_state != CDC_TX_READY || cdc_tx_len >= sizeof(cdc_data_tx))
        return 0;

    cdc_data_tx [cdc_tx_len++] = c;
    return sizeof(cdc_data_tx) - cdc_tx_len;
}

/*
 * cdc_tx_service handles device-to-host transaction(s). This function
 * should be called once per Main Program loop after the device reaches
 * the configured state.
 *
 * Usage:
 *   void main()
 *   {
 *       usb_device_init();
 *       while (1)
 *       {
 *           usb_device_tasks();
 *           if (USBGetDeviceState() < CONFIGURED_STATE || USBIsDeviceSuspended())
 *           {
 *               // Either the device is not configured or we are suspended
 *               // so we don't want to do execute any application code
 *               continue;   // go back to the top of the while loop
 *           } else {
 *               // Keep trying to send data to the PC as required
 *               cdc_tx_service();
 *
 *               // Run application code.
 *               UserApplication();
 *           }
 *       }
 *   }
 */
void cdc_tx_service()
{
    // Check that USB connection is established.
    if (usb_device_state < CONFIGURED_STATE ||
        (U1PWRC & PIC32_U1PWRC_USUSPEND))
        return;

    if (usb_handle_busy(data_in))
        return;
    /*
     * Completing stage is necessary while [ mCDCUSartTxIsBusy()==1 ].
     * By having this stage, user can always check cdc_trf_state,
     * and not having to call mCDCUsartTxIsBusy() directly.
     */
    if (cdc_trf_state == CDC_TX_COMPLETING) {
        cdc_trf_state = CDC_TX_READY;
        cdc_tx_len = 0;
    }

    /*
     * If CDC_TX_BUSY_ZLP state, send zero length packet
     */
    if (cdc_trf_state == CDC_TX_BUSY_ZLP)
    {
        data_in = usb_tx_one_packet (CDC_DATA_EP, 0, 0);
        cdc_trf_state = CDC_TX_COMPLETING;
        return;
    }

    /*
     * Send a next packet.
     */
    if (cdc_trf_state == CDC_TX_READY && cdc_tx_len > 0) {
        /*
         * Determine if a zero length packet state is necessary.
         * See explanation in USB Specification 2.0: Section 5.8.3
         */
        if (cdc_tx_len == CDC_DATA_IN_EP_SIZE)
            cdc_trf_state = CDC_TX_BUSY_ZLP;
        else
            cdc_trf_state = CDC_TX_COMPLETING;

        data_in = usb_tx_one_packet (CDC_DATA_EP, (unsigned char*)&cdc_data_tx, cdc_tx_len);
    }
}
