/***********************************************************************
 * $Id:: mw_usbd_cdcuser.c 1728 2014-09-19 17:56:35Z nxp73930                  $
 *
 * Project: USB device ROM Stack
 *
 * Description:
 *     USB Communication Device Class User module.
 *
 ***********************************************************************
 *   Copyright(C) 2011, NXP Semiconductor
 *   All rights reserved.
 *
 * Software that is described herein is for illustrative purposes only
 * which provides customers with programming information regarding the
 * products. This software is supplied "AS IS" without any warranties.
 * NXP Semiconductors assumes no responsibility or liability for the
 * use of the software, conveys no license or title under any patent,
 * copyright, or mask work right to the product. NXP Semiconductors
 * reserves the right to make changes in the software without
 * notification. NXP Semiconductors also make no representation or
 * warranty that such application will be suitable for the specified
 * use without further testing or modification.
 **********************************************************************/
#include <string.h>	/*for memcpy */
#include "mw_usbd.h"
#include "mw_usbd_core.h"
#include "mw_usbd_hw.h"
#include "mw_usbd_cdc.h"
#include "mw_usbd_cdcuser.h"

#ifndef FALSE
#define FALSE 0
#define TRUE !FALSE
#endif

/*
 *  CDC Send Class Notification routine
 *  Parameters:     hCdc: Handle to the CDC Control Structure.
 *                                  bNotification: Notification type
 *									data: Notification data
 *  Return Value:   ErrorCode_t type to indicate success or error condition.
 */

/*----------------------------------------------------------------------------
   Send the SERIAL_STATE notification as defined in usbcdc11.pdf, 6.3.5.
*---------------------------------------------------------------------------*/
ErrorCode_t mwCDC_SendNotification(USBD_HANDLE_T hCdc, uint8_t bNotification, uint16_t data)
{
	USB_CDC_CTRL_T *pCdcCtrl = (USB_CDC_CTRL_T *) hCdc;

	uint16_t len = 8;

	pCdcCtrl->notice_buf[0] = 0xA1;							/* bmRequestType */
	pCdcCtrl->notice_buf[1] = bNotification;
	pCdcCtrl->notice_buf[2] = 0x00;							/* wValue */
	pCdcCtrl->notice_buf[3] = 0x00;
	pCdcCtrl->notice_buf[4] = 0x00;							/* wIndex (Interface #, LSB first) */
	pCdcCtrl->notice_buf[5] = 0x00;
	pCdcCtrl->notice_buf[6] = 0x00;							/* wLength (Data length = 2 bytes, LSB first) */
	pCdcCtrl->notice_buf[7] = 0x00;

	switch (bNotification) {
	case CDC_NOTIFICATION_NETWORK_CONNECTION:
		if ( data) {
			pCdcCtrl->notice_buf[2] = 1;
		}
		break;

	case CDC_RESPONSE_AVAILABLE:
		break;

	case CDC_NOTIFICATION_SERIAL_STATE:
		pCdcCtrl->notice_buf[6] = 0x02;				/* wLength (Data length = 2 bytes, LSB first) */
		pCdcCtrl->notice_buf[8] = (data >>  0) & 0xFF;	/* UART State Bitmap (16bits, LSB first) */
		pCdcCtrl->notice_buf[9] = (data >>  8) & 0xFF;
		len = 10;

	default:
		return ERR_API_INVALID_PARAM2;
	}
	pCdcCtrl->pUsbCtrl->hw_api->WriteEP(pCdcCtrl->pUsbCtrl, pCdcCtrl->epint_num,
										&pCdcCtrl->notice_buf[0], len);	/* send notification */

	return LPC_OK;
}

/*
 *  CDC Get Request Callback routine
 *  Parameters:     hCdc: Handle to the CDC Control Structure.
 *                                  pSetup: Pointer to setup packet received from host.
 *									pBuffer: Pointer to a pointer of data buffer containing response data.
 *									length: Amount of data to be sent back to host.
 *  Return Value:   ErrorCode_t type to indicate success or error condition.
 */

ErrorCode_t mwCDC_GetRequest(USBD_HANDLE_T hCdc, USB_SETUP_PACKET *pSetup, uint8_t * *pBuffer, uint16_t *length)
{
	USB_CDC_CTRL_T *pCdcCtrl = (USB_CDC_CTRL_T *) hCdc;
	ErrorCode_t ret = ERR_USBD_UNHANDLED;

	switch (pSetup->bRequest) {
	case CDC_GET_ENCAPSULATED_RESPONSE:
		if (pCdcCtrl->GetEncpsResp) {
			ret = pCdcCtrl->GetEncpsResp(hCdc, pBuffer, length);
			break;
		}

	/* fall through */
	case CDC_GET_COMM_FEATURE:
		if (pCdcCtrl->GetCommFeature) {
			ret = pCdcCtrl->GetCommFeature(hCdc, pSetup->wValue.W, pBuffer, length);
			break;
		}
		else {
			memset(*pBuffer, 0, USB_MAX_PACKET0);
		}
		ret = LPC_OK;
		break;

	case CDC_GET_LINE_CODING:
		*pBuffer = (uint8_t *) &pCdcCtrl->line_coding;
		*length = 7;
		ret = LPC_OK;
		break;
	}
	return ret;
}

/*
 *  CDC Set Request Callback routine
 *  Parameters:     hCdc: Handle to the CDC Control Structure.
 *                                  pSetup: Pointer to setup packet received from host.
 *									pBuffer: Pointer to a pointer of data buffer containing request data.
 *									length: Amount of data copied to destination buffer.
 *  Return Value:   ErrorCode_t type to indicate success or error condition.
 */

ErrorCode_t mwCDC_SetRequest(USBD_HANDLE_T hCdc, USB_SETUP_PACKET *pSetup, uint8_t * *pBuffer, uint16_t length)
{
	USB_CDC_CTRL_T *pCdcCtrl = (USB_CDC_CTRL_T *) hCdc;
	ErrorCode_t ret = LPC_OK;

	switch (pSetup->bRequest) {
	case CDC_SEND_ENCAPSULATED_COMMAND:
		if ((length != 0 ) && (pCdcCtrl->SendEncpsCmd != 0)) {
			ret = pCdcCtrl->SendEncpsCmd(hCdc, *pBuffer, length);
		}
		break;

	case CDC_SET_COMM_FEATURE:
		if ((length != 0 ) && (pCdcCtrl->SetCommFeature != 0)) {
			ret = pCdcCtrl->SetCommFeature(hCdc, pSetup->wValue.W, *pBuffer, length);
		}
		break;

	case CDC_CLEAR_COMM_FEATURE:
		if (pCdcCtrl->ClrCommFeature) {
			ret = pCdcCtrl->ClrCommFeature(hCdc, pSetup->wValue.W);
		}
		break;

	case CDC_SET_CONTROL_LINE_STATE:
		if (pCdcCtrl->SetCtrlLineState) {
			ret = pCdcCtrl->SetCtrlLineState(hCdc, pSetup->wValue.W);
		}
		break;

	case CDC_SEND_BREAK:
		if (pCdcCtrl->SendBreak) {
			ret = pCdcCtrl->SendBreak(hCdc, pSetup->wValue.W);
		}
		break;

	case CDC_SET_LINE_CODING:

		if (length != 0) {
			/* line coding data should have been copied to the data member */
			if (pCdcCtrl->SetLineCode) {
				ret = pCdcCtrl->SetLineCode(hCdc, &pCdcCtrl->line_coding);
				break;
			}
		}
		else {
			/* copy line coding data directly into the structure */
			*pBuffer = (uint8_t *) &pCdcCtrl->line_coding;
		}
		break;

	default:
		ret = ERR_USBD_UNHANDLED;
		break;
	}
	return ret;
}

/*
 *  Default CDC Class Handler
 *  Parameters:     hUsb: Handle to the USB device stack.
 *                                  data: Pointer to the data which will be passed when callback function is called by the stack.
 *									event:  Type of endpoint event.
 *  Return Value:   ErrorCode_t type to indicate success or error condition.
 */

ErrorCode_t mwCDC_ep0_hdlr(USBD_HANDLE_T hUsb, void *data, uint32_t event)
{
	USB_CORE_CTRL_T *pCtrl = (USB_CORE_CTRL_T *) hUsb;
	USB_CDC_CTRL_T *pCdcCtrl = (USB_CDC_CTRL_T *) data;
	ErrorCode_t ret = ERR_USBD_UNHANDLED;

	switch (event) {
	case USB_EVT_SETUP:
		if ((pCtrl->SetupPacket.bmRequestType.BM.Type == REQUEST_CLASS) &&
			(pCtrl->SetupPacket.bmRequestType.BM.Recipient == REQUEST_TO_INTERFACE) &&
			((pCtrl->SetupPacket.wIndex.WB.L == pCdcCtrl->cif_num)  ||		/* IF number correct? */
			 (pCtrl->SetupPacket.wIndex.WB.L == pCdcCtrl->dif_num)) ) {

			/* assign default EP0 buffer for data transfer */
			pCtrl->EP0Data.pData = pCtrl->EP0Buf;					/* data to be received/sent */
			/* check if it is set request */
			if ( pCtrl->SetupPacket.bmRequestType.BM.Dir == REQUEST_HOST_TO_DEVICE) {
				/* ask user where to recieve the data associated with request */
				ret = pCdcCtrl->CIC_SetRequest(pCdcCtrl, &pCtrl->SetupPacket,
											   &pCtrl->EP0Data.pData, 0);
				if ( (ret == LPC_OK) && (pCtrl->SetupPacket.wLength == 0)) {
					mwUSB_StatusInStage(pCtrl);			/* send status handshake */
				}
			}
			else {
				/* allow user to copy data to EP0Buf or change the pointer to his own buffer */
				ret = pCdcCtrl->CIC_GetRequest(pCdcCtrl, &pCtrl->SetupPacket,
											   &pCtrl->EP0Data.pData, &pCtrl->EP0Data.Count);
				if (ret == LPC_OK) {
					mwUSB_DataInStage(pCtrl);							/* send requested data */
				}
			}
		}
		break;

	case USB_EVT_OUT:
		if ((pCtrl->SetupPacket.bmRequestType.BM.Type == REQUEST_CLASS) &&
			(pCtrl->SetupPacket.bmRequestType.BM.Recipient == REQUEST_TO_INTERFACE) &&
			((pCtrl->SetupPacket.wIndex.WB.L == pCdcCtrl->cif_num)  ||		/* IF number correct? */
			 (pCtrl->SetupPacket.wIndex.WB.L == pCdcCtrl->dif_num)) ) {

			pCtrl->EP0Data.pData -= pCtrl->SetupPacket.wLength;
			ret = pCdcCtrl->CIC_SetRequest(pCdcCtrl, &pCtrl->SetupPacket, &pCtrl->EP0Data.pData,
										   pCtrl->SetupPacket.wLength);
			if ( ret == LPC_OK) {

				mwUSB_StatusInStage(pCtrl);						/* send Acknowledge */
			}
		}
		break;

	default:
		break;
	}
	return ret;
}

/**
 * @brief   Get memory required by CDC class.
 * @param [in/out] param parameter structure used for initialisation.
 * @retval  Length required for CDC data structure and buffers.
 *
 * Example Usage:
 * @code
 *    mem_req = mwCDC_GetMemSize(param);
 * @endcode
 */
uint32_t mwCDC_GetMemSize(USBD_CDC_INIT_PARAM_T *param)
{
	uint32_t req_len = 0;

	/* calculate required length */
	req_len += sizeof(USB_CDC_CTRL_T);	/* memory for MSC controller structure */
	req_len += 4;	/* for alignment overhead */
	req_len &= ~0x3;

	return req_len;
}

/*
 *  MSC function initialization routine
 *  Parameters:     hUsb: Handle to the USB device stack.
 *                                  param: Structure containing CDC function driver module
 *						      initialization parameters.
 *									phCDC: Handle to CDC Control Structure
 *  Return Value:   ErrorCode_t type to indicate success or error condition.
 */

ErrorCode_t mwCDC_init(USBD_HANDLE_T hUsb, USBD_CDC_INIT_PARAM_T *param, USBD_HANDLE_T *phCDC)
{
	ErrorCode_t ret = LPC_OK;
	uint32_t new_addr, i;
	USB_CDC_CTRL_T *pCdcCtrl;
	USB_ENDPOINT_DESCRIPTOR *pEpDesc;
	USB_INTERFACE_DESCRIPTOR *pIntfDesc = (USB_INTERFACE_DESCRIPTOR *) param->cif_intf_desc;

	/* check for memory alignment */
	if ((param->mem_base &  0x3) &&
		(param->mem_size < sizeof(USB_CDC_CTRL_T))) {
		return ERR_USBD_BAD_MEM_BUF;
	}

	/* allocate memory for the control data structure */
	pCdcCtrl = (USB_CDC_CTRL_T *) param->mem_base;
	param->mem_base += sizeof(USB_CDC_CTRL_T);
	param->mem_size -= sizeof(USB_CDC_CTRL_T);
	/* align to 4 byte boundary */
	param->mem_base += 0x03;
	param->mem_base &= ~0x03;
	param->mem_size &= ~0x03;

	/* Init control structures with passed params */
	memset((void *) pCdcCtrl, 0, sizeof(USB_CDC_CTRL_T));
	pCdcCtrl->pUsbCtrl = (USB_CORE_CTRL_T *) hUsb;
	/* copy default handlers */
	pCdcCtrl->CIC_GetRequest = mwCDC_GetRequest;
	pCdcCtrl->CIC_SetRequest = mwCDC_SetRequest;
	/* copy user defined call backs functions*/
	pCdcCtrl->SendEncpsCmd = param->SendEncpsCmd;
	pCdcCtrl->GetEncpsResp = param->GetEncpsResp;
	pCdcCtrl->SetCommFeature = param->SetCommFeature;
	pCdcCtrl->GetCommFeature = param->GetCommFeature;
	pCdcCtrl->ClrCommFeature = param->ClrCommFeature;
	pCdcCtrl->SetCtrlLineState = param->SetCtrlLineState;
	pCdcCtrl->SendBreak = param->SendBreak;
	pCdcCtrl->SetLineCode = param->SetLineCode;

	/* parse the control interface descriptor */
	if ((pIntfDesc->bDescriptorType == USB_INTERFACE_DESCRIPTOR_TYPE) &&
		(pIntfDesc->bInterfaceClass == CDC_COMMUNICATION_INTERFACE_CLASS) &&
		(pIntfDesc->bNumEndpoints < 2) ) {

		/* store interface number */
		pCdcCtrl->cif_num = pIntfDesc->bInterfaceNumber;
		if (pIntfDesc->bNumEndpoints == 1) {
			new_addr = (uint32_t) pIntfDesc + pIntfDesc->bLength;
			/* move to next descriptor */
			do {
				pEpDesc = (USB_ENDPOINT_DESCRIPTOR *) new_addr;
				new_addr = (uint32_t) pEpDesc + pEpDesc->bLength;

				/* parse endpoint descriptor */
				if ((pEpDesc->bDescriptorType == USB_ENDPOINT_DESCRIPTOR_TYPE) &&
					(pEpDesc->bmAttributes == USB_ENDPOINT_TYPE_INTERRUPT)) {

					/* store INTERRUPT IN endpoint */
					pCdcCtrl->epint_num = pEpDesc->bEndpointAddress;
					break;
				}
			} while (pEpDesc->bDescriptorType != USB_INTERFACE_DESCRIPTOR_TYPE);

			if (pCdcCtrl->epint_num == 0) {
				return ERR_USBD_BAD_EP_DESC;
			}
		}
	}
	else {
		return ERR_USBD_BAD_INTF_DESC;
	}

	/* parse the data interface descriptor */
	pIntfDesc = (USB_INTERFACE_DESCRIPTOR *) param->dif_intf_desc;
	if ((pIntfDesc->bDescriptorType == USB_INTERFACE_DESCRIPTOR_TYPE) &&
		(pIntfDesc->bInterfaceClass == CDC_DATA_INTERFACE_CLASS) &&
		(pIntfDesc->bInterfaceSubClass == 0x00) ) {

		/* store interface number */
		pCdcCtrl->dif_num = pIntfDesc->bInterfaceNumber;
		new_addr = (uint32_t) pIntfDesc + pIntfDesc->bLength;
		/* move to next descriptor */
		for (i = 0; i < pIntfDesc->bNumEndpoints; i++) {
			pEpDesc = (USB_ENDPOINT_DESCRIPTOR *) new_addr;
			new_addr = (uint32_t) pEpDesc + pEpDesc->bLength;

			/* parse endpoint descriptor */
			if ((pEpDesc->bDescriptorType == USB_ENDPOINT_DESCRIPTOR_TYPE) &&
				(pEpDesc->bmAttributes == USB_ENDPOINT_TYPE_BULK)) {

				if (pEpDesc->bEndpointAddress & USB_ENDPOINT_DIRECTION_MASK) {
					/* store BULK IN endpoint */
					pCdcCtrl->epin_num = pEpDesc->bEndpointAddress;
				}
				else {
					/* store BULK OUT endpoint */
					pCdcCtrl->epout_num = pEpDesc->bEndpointAddress;
				}
			}
		}
	}
	else {
		return ERR_USBD_BAD_INTF_DESC;
	}

	if ( (pCdcCtrl->epin_num == 0) || (pCdcCtrl->epout_num == 0) || (ret != LPC_OK) ) {
		return ERR_USBD_BAD_EP_DESC;
	}

	/* register ep0 handler */
	/* check if user wants his own handler */
	if (param->CDC_Ep0_Hdlr == 0) {
		ret = mwUSB_RegisterClassHandler(hUsb, mwCDC_ep0_hdlr, pCdcCtrl);
	}
	else {
		ret = mwUSB_RegisterClassHandler(hUsb, param->CDC_Ep0_Hdlr, pCdcCtrl);
		param->CDC_Ep0_Hdlr = mwCDC_ep0_hdlr;
	}
	/* return the handle */
	*phCDC = (USBD_HANDLE_T) pCdcCtrl;

	return ret;
}
