/*******************************************************************************
 *
 *      A U D I O   R E C O R D E R   U S E R  U S B   F U N C T I O N S
 *
 *      Copyright A Levido 2013 - All Rights Reserved
 *
 ******************************************************************************/
#ifdef __XC32
    #include <xc.h>
#endif

#include "system.h"
#include "./USB/usb.h"
#include "USB/usb_function_msd.h"
#include "sdcard.h"

extern void doUSBAttach(void);

/* Associate the spi media driver functions with the usb msd function driver */
LUN_FUNCTIONS LUN[MAX_LUN + 1] =
{
    {
        msdInitMedia,
        msdReadCapacity,
        msdReadSectorSize,
        msdMediaDetect,
        msdSectorRead,
        msdReadWP,
        msdSectorWrite
    }
};

/* Standard Response to INQUIRY command stored in ROM 	*/
const ROM InquiryResponse inq_resp = {
	0x00,		// peripheral device is connected, direct access block device
	0x80,           // removable
	0x04,	 	// version = 00=> does not conform to any standard, 4=> SPC-2
	0x02,		// response is in format specified by SPC-2
	0x20,		// n-4 = 36-4=32= 0x20
	0x00,		// sccs etc.
	0x00,		// bque=1 and cmdque=0,indicates simple queueing 00 is obsolete,
			// but as in case of other device, we are just using 00
	0x00,		// 00 obsolete, 0x80 for basic task queueing
	{'I','m','b','r','i','u','s',' '
    },
	// this is the T10 assigned Vendor ID
	{'M','a','s','s',' ','S','t','o','r','a','g','e',' ',' ',' ',' '
    },
	{'0','0','0','1'
    }
};

/* USB Callback Functions *****************************************************/

/** USB Suspend Callback
 * Host requires us to suspend. If bus-powered must reduce energy consumption
 * below 2.5 mA */
void USBCBSuspend(void)
{

}

/** USB Wake from Suspend Callback
 * Host has awoken. Must come out of powerdown now. */
void USBCBWakeFromSuspend(void)
{

}

/** USB SOF Packet Sent callback
 * The SOF packet is sent by the host every 1 ms to allow synchronisation
 * for isochronous transfers*/
void USBCB_SOF_Handler(void)
{

}

/** USB Error Callback
 * Maily used during debug. Read UEIR to see what error occurred */
void USBCBErrorHandler(void)
{

}

/** USB Other Request Callback
 * Host sends SETUP packets to request actions or information. Many of these
 * are handled by the usb_device.c functions. Others come through this callback
 * an must be handled by the device function routines */

void USBCBCheckOtherReq(void)
{
    USBCheckMSDRequest();
}

/** USB Set Descriptor Request handler callback
 * Not used in most applications. If used, must claim session ownership.*/
void USBCBStdSetDscHandler(void)
{

}

/** USB Initialise Endpoints Callback
 * This function is called when the device becomes initialized, which occurs
 * after the host sends a SET_CONFIGURATION (wValue not = 0) request.  This
 * callback function should initialize the endpoints for the device's usage
 * according to the current configuration */
void USBCBInitEP(void)
{
    #if (MSD_DATA_IN_EP == MSD_DATA_OUT_EP)
        USBEnableEndpoint(MSD_DATA_IN_EP,USB_IN_ENABLED|USB_OUT_ENABLED|USB_HANDSHAKE_ENABLED|USB_DISALLOW_SETUP);
    #else
        USBEnableEndpoint(MSD_DATA_IN_EP,USB_IN_ENABLED|USB_HANDSHAKE_ENABLED|USB_DISALLOW_SETUP);
        USBEnableEndpoint(MSD_DATA_OUT_EP,USB_OUT_ENABLED|USB_HANDSHAKE_ENABLED|USB_DISALLOW_SETUP);
    #endif

        doUSBAttach();
}

/** USB Send Resume Function
 * The USB specifications allow some types of USB peripheral
 * devices to wake up a host PC (such as if it is in a low power suspend to
 * RAM state). This function may be called by application firmware to wake up
 * the host.  This function will only be able to wake up the host if all of the
 * below are true:
 *
 * 1.  The USB driver used on the host PC supports the remote wakeup capability.
 * 2.  The USB configuration descriptor indicates the device is remote wakeup
 *     capable in the bmAttributes field.
 * 3.  The USB host PC is currently sleeping, and has previously sent your
 *     device a SET FEATURE setup packet which "armed" the remote wakeup
 *     capability.
 *
 * If the host has not armed the device to perform remote wakeup, then this
 * function will return without actually performing a remote wakeup sequence.
 * This is the required behavior, as a USB device that has not been armed to
 * perform remote  wakeup must not drive remote wakeup signalling onto the bus;
 *  doing so will cause USB compliance testing failure.
 *
 * This callback should send a RESUME signal that has the period of 1-15ms */

void USBCBSendResume(void)
{
    static WORD delay_count;

    //First verify that the host has armed us to perform remote wakeup.
    //It does this by sending a SET_FEATURE request to enable remote wakeup,
    //usually just before the host goes to standby mode
    if(USBGetRemoteWakeupStatus() == TRUE)
    {
        if(USBIsBusSuspended() == TRUE)
        {
            USBMaskInterrupts();
            USBCBWakeFromSuspend();
            USBSuspendControl = 0;
            USBBusIsSuspended = FALSE;
            //Section 7.1.7.7 of the USB 2.0 specifications indicates a USB
            //device must continuously see 5ms+ of idle on the bus, before it sends
            //remote wakeup signalling.
            delay_count = 3600U;
            do
            {
                delay_count--;
            }while(delay_count);

            //Now drive the resume K-state signalling onto the USB bus
            USBResumeControl = 1;
            delay_count = 1800U;        // Set RESUME line for 1-13 ms
            do
            {
                delay_count--;
            }while(delay_count);
            USBResumeControl = 0;       //Finished driving resume signalling
            USBUnmaskInterrupts();
        }
    }
}

/** Handle the USB events and call the appropriate functions*/
BOOL USER_USB_CALLBACK_EVENT_HANDLER(USB_EVENT event, void *pdata, WORD size)
{
    switch((INT)event)
    {
        case EVENT_TRANSFER:
            //Add application specific callback task or function here 
            break;
        case EVENT_SOF:
            USBCB_SOF_Handler();
            break;
        case EVENT_SUSPEND:
            USBCBSuspend();
            break;
        case EVENT_RESUME:
            USBCBWakeFromSuspend();
            break;
        case EVENT_CONFIGURED:
            USBCBInitEP();
            break;
        case EVENT_SET_DESCRIPTOR:
            USBCBStdSetDscHandler();
            break;
        case EVENT_EP0_REQUEST:
            USBCBCheckOtherReq();
            break;
        case EVENT_BUS_ERROR:
            USBCBErrorHandler();
            break;
        case EVENT_TRANSFER_TERMINATED:
            /* Check if the host recently did a clear endpoint halt on the
             * MSD OUT endpoint. In this case, we want to re-arm the MSD OUT
             * endpoint, so we are prepared to receive the next CBW that the
             * host might want to send. */
            if(MSDWasLastCBWValid() == FALSE)
            {
                /* Need to re-stall the endpoints for persistent STALL */
                USBStallEndpoint(MSD_DATA_IN_EP, IN_TO_HOST);
      		USBStallEndpoint(MSD_DATA_OUT_EP, OUT_FROM_HOST);
            }
            else
            {
                /* Check if the host cleared halt on the bulk out endpoint.
                 * In this case, we should re-arm the endpoint, so we can
                 * receive the next CBW */
                if((USB_HANDLE)pdata == USBGetNextHandle(MSD_DATA_OUT_EP, OUT_FROM_HOST))
                {
                    USBMSDOutHandle = USBRxOnePacket(MSD_DATA_OUT_EP, (BYTE*)&msd_cbw, MSD_OUT_EP_SIZE);
                }
            }
            break;
        default:
            break;
    }
    return TRUE;
}
