//
//  usbh_wcc_bluetooth.c
//
//  USB Host Protocol Stack - USB Wireless controller class for bluetooth dongle
//  Copyright (C) 2013-2017 Toyohiko Togashi tog001@nifty.com
//
//
//  This program is free software; you can redistribute it and/or modify it under the terms of the
//  GNU General Public License as published by the Free Software Foundation; either version 3
//  of the License, or (at your option) any later version.
//
//  This program 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 General Public License for more details.
//
//  You should have received a copy of the GNU General Public License along with this program.
//  If not, see <http://www.gnu.org/licenses/>
//
//
//	Reference:
//		Bluetooth SIG (www.bluetooth.com)
//			BLUETOOTH SPECIFICATION Version 4.0
//				[Vol 4] Host Controller Interface [Transport Layer]
//				Part B: USB Transport Layer
//		STMicroelectronics
//			STM32 USB HOST Library V3.2.2 / 07-July-2015 (STM32Cube FW_F4 V1.15.0)
//
//	Device:
//		STM32Fxx
//
//	Update history
//	---------- ----- -------------------------------------------
//	2013.01.21 v0.0  First cording
//	2013.04.21 v0.1  Commit
//	2013.12.17 v0.1a Bug fix
//	2014.03.05 v0.2  Change the USB process calling, all new
//	2017.04.23 v0.2  STM32Cube FW_F4 V1.15.0, all new
//
#include <string.h>
#include "usbh_core.h"
#include "usbh_wcc_bluetooth.h"
#include "bt_hci.h"
#include "bt_process.h"

#define MIN(a,b)	((a) < (b) ? (a) : (b))
#define MAX(a,b)	((a) > (b) ? (a) : (b))
//#define DEBUG1
//#define DEBUG2

// DEBUG tools
#ifdef DEBUG1
#include <stdio.h>
#define DEBUGP(...)		printf(__VA_ARGS__)
#define DEBUG_DUMP(s,b,l) { int i; printf("%s",s); for(i=0; i<l; i++) printf("%02x", (b)[i]); printf("\n"); }
#else
#define DEBUGP(...)
#define DEBUG_DUMP(s,b,l)
#endif


//	Configure
//#define USBH_WCC_BLUETOOTH_TASK_SCHEDULE_1		// Handler task switch method: Sequential event
//#define USBH_WCC_BLUETOOTH_TASK_SCHEDULE_2	1	//                           : Parallel event, rotation
//#define USBH_WCC_BLUETOOTH_TASK_SCHEDULE_2	2	//                           : Parallel event, batch
  #define USBH_WCC_BLUETOOTH_TASK_SCHEDULE_2	3	//                           : Parallel event, rotation B
#define USBH_WCC_BLUETOOTH_TIME_OUT			5		// [sec] Fail safe
#define USBH_WCC_BLUETOOTH_INTR_POLL_MIN	10		// Minimum poling wait time (USB frame count)


//------------------------------------------------------------------------------
//	USB pluged in
//------------------------------------------------------------------------------
USBH_StatusTypeDef USBH_WCC_Bluetooth_InterfaceInit(USBH_HandleTypeDef *phost){
	uint8_t						interface;
	USBH_StatusTypeDef			status;
	WCC_Bluetooth_HandleTypeDef	*handle;

	status = USBH_OK;
	interface = USBH_FindInterface(phost, phost->pActiveClass->ClassCode, USB_WCC_CLASS_RF, USB_PROTOCOL_BLUETOOTH);
	if (interface == 0xFF) {	/* Not Valid Interface */
		USBH_ErrLog("Cannot Find the interface for %s class.", phost->pActiveClass->Name);
		status = USBH_FAIL;
	} else {
		int i;

		USBH_SelectInterface (phost, interface);
		handle = (WCC_Bluetooth_HandleTypeDef *)USBH_malloc (sizeof(WCC_Bluetooth_HandleTypeDef));
		USBH_memset(handle, 0, sizeof(WCC_Bluetooth_HandleTypeDef));
		phost->pActiveClass->pData = handle;
		for(i = 0; i < phost->device.CfgDesc.Itf_Desc[0].bNumEndpoints; i++) {
			uint8_t		a;
			uint16_t	p;
			uint8_t		h;

			a = phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[i].bEndpointAddress;
			p = phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[i].wMaxPacketSize;
		    h = USBH_AllocPipe(phost, a);
			switch(a) {
			case 0x81:					// HCI Events pipe
			    USBH_OpenPipe(phost, h, a, phost->device.address, phost->device.speed, USB_EP_TYPE_INTR, p);
				handle->intrEp     = a;
				handle->intrEpSize = p;
				handle->intrHcNum  = h;
				handle->poll       = MAX(phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[i].bInterval, USBH_WCC_BLUETOOTH_INTR_POLL_MIN);
				break;
			case 0x82:					// ACL Data IN pipe
			    USBH_OpenPipe(phost, h, a, phost->device.address, phost->device.speed, USB_EP_TYPE_BULK, p);
				handle->inEp       = a;
				handle->inEpSize   = p;
				handle->inHcNum    = h;
				break;
			case 0x02:					// ACL Data OUT pipe
			    USBH_OpenPipe(phost, h, a, phost->device.address, phost->device.speed, USB_EP_TYPE_BULK, p);
				handle->outEp      = a;
				handle->outEpSize  = p;
				handle->outHcNum   = h;
				break;
			default:
				status = USBH_UNRECOVERED_ERROR;
				break;
			}
		}
		if (status == USBH_OK) {
		    USBH_LL_SetToggle(phost, handle->intrEp, 0);
		    USBH_LL_SetToggle(phost, handle->inEp,   0);
		    USBH_LL_SetToggle(phost, handle->outEp,  0);
			bt_init();
			bt_setAliveHCI();				// To L2CAP communication path
		}
	}
	USBH_DbgLog("USBH_WCC_Bluetooth_InterfaceInit: status=%d", status);
	return(status);
}

//------------------------------------------------------------------------------
//	USB unpluged
//------------------------------------------------------------------------------
USBH_StatusTypeDef USBH_WCC_Bluetooth_InterfaceDeInit (USBH_HandleTypeDef *phost){
	WCC_Bluetooth_HandleTypeDef *handle;

	handle = (WCC_Bluetooth_HandleTypeDef *)(phost->pActiveClass->pData);
	if (handle->intrHcNum) {
		USBH_ClosePipe(phost, handle->intrHcNum);
		USBH_FreePipe (phost, handle->intrHcNum);
		handle->intrHcNum = 0;
	}
	if (handle->inHcNum) {
		USBH_ClosePipe(phost, handle->inHcNum);
		USBH_FreePipe (phost, handle->inHcNum);
		handle->inHcNum = 0;
	}
	if (handle->outHcNum) {
		USBH_ClosePipe(phost, handle->outHcNum);
		USBH_FreePipe (phost, handle->outHcNum);
		handle->outHcNum = 0;
	}
	if (phost->pActiveClass->pData) {
		USBH_free(phost->pActiveClass->pData);
		phost->pActiveClass->pData = 0;
	}
	bt_setDeadHCI();					// To L2CAP communication path
	bt_deinit();
	USBH_DbgLog("USBH_WCC_Bluetooth_InterfaceDeInit: OK");
	return(USBH_OK);
}

//------------------------------------------------------------------------------
//	Class Request
//------------------------------------------------------------------------------
USBH_StatusTypeDef USBH_WCC_Bluetooth_ClassRequest(USBH_HandleTypeDef *phost) {
	USBH_DbgLog("USBH_WCC_Bluetooth_ClassRequest: OK");
	return(USBH_OK);
}

//------------------------------------------------------------------------------
//	Process
//------------------------------------------------------------------------------
USBH_StatusTypeDef USBH_WCC_Bluetooth_ProcessCmd(USBH_HandleTypeDef *phost) {
	USBH_StatusTypeDef			status;
	int							rc;
	WCC_Bluetooth_HandleTypeDef *handle;

	handle = (WCC_Bluetooth_HandleTypeDef *)(phost->pActiveClass->pData);
	status = USBH_OK;
	switch(handle->cmdState){
	case BT_CMD_IDLE:
	case BT_CMD_START:
		rc = bt_getHCIcommand(handle->cmdBuf, sizeof(handle->cmdBuf));
		if (rc > 0) {
			handle->cmdTimeOut = time(NULL) + USBH_WCC_BLUETOOTH_TIME_OUT;
			handle->cmdBufLen  = rc;
			handle->cmdState   = BT_CMD_POST;
			DEBUG_DUMP("CtrlSend:", handle->cmdBuf, handle->cmdBufLen);
		}
		break;
	case BT_CMD_POST:
		phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_TYPE_CLASS | USB_REQ_RECIPIENT_DEVICE;
		phost->Control.setup.b.bRequest      = 0;
		phost->Control.setup.b.wValue.w      = 0;
		phost->Control.setup.b.wIndex.w      = 0;		// Descriptor interface#
		phost->Control.setup.b.wLength.w     = handle->cmdBufLen;
		status = USBH_CtlReq(phost, handle->cmdBuf, handle->cmdBufLen);
		if (status == USBH_OK) {
			handle->cmdState = BT_CMD_IDLE;
		} else if (status == USBH_BUSY) {
			if (handle->cmdTimeOut < time(NULL)) {
				USBH_ErrLog("CtrlSend: time out");
				status = USBH_UNRECOVERED_ERROR;
				handle->cmdState = BT_CMD_IDLE;
			}
		} else {
			USBH_ErrLog("CtrlSend: error status=%d", status);
			handle->cmdState = BT_CMD_IDLE;
			break;
		}
		break;
	default:
		handle->cmdState = BT_CMD_IDLE;
		break;
	}
	return(status);
}

USBH_StatusTypeDef USBH_WCC_Bluetooth_ProcessEvt(USBH_HandleTypeDef *phost) {
	USBH_StatusTypeDef			status;
	USBH_URBStateTypeDef		urbState;
	WCC_Bluetooth_HandleTypeDef *handle;

	handle = (WCC_Bluetooth_HandleTypeDef *)(phost->pActiveClass->pData);
	status = USBH_OK;
	switch(handle->evtState){
	case BT_EVT_IDLE:
		if (handle->intrHcNum != 0) {
			handle->evtState = BT_EVT_SYNC;
		}
	    break;
	case BT_EVT_SYNC:
	    if (phost->Timer & 1) {		// Sync with start of Even Frame
			handle->evtState    = BT_EVT_GET_DATA;
	    }
	    break;
	case BT_EVT_GET_DATA:
		USBH_InterruptReceiveData(phost, handle->evtBuf, MIN(handle->intrEpSize, sizeof(handle->evtBuf)), handle->intrHcNum);
		handle->timer      = phost->Timer;
		handle->evtTimeOut = time(NULL) + USBH_WCC_BLUETOOTH_TIME_OUT;
		handle->evtFlag    = 0;
		handle->evtState   = BT_EVT_POLL;
		break;
	case BT_EVT_POLL:
		urbState = USBH_LL_GetURBState(phost, handle->intrHcNum);
		if (urbState == USBH_URB_DONE) {
			if (handle->evtFlag == 0) {
				handle->evtFlag = 1;
				handle->evtBufLen = USBH_LL_GetLastXferSize(phost, handle->intrHcNum);
				DEBUG_DUMP("IntrRecv:", handle->evtBuf, handle->evtBufLen);
				handle->evtState  = BT_EVT_POST_WAIT;
				if (bt_putHCIevent(handle->evtBuf, handle->evtBufLen) == handle->evtBufLen) {
					handle->evtState = BT_EVT_GET_DATA;
				}
			} else {
				USBH_DbgLog("IntrRecv: duplicate skip\n");
			}
		} else if (urbState == USBH_URB_IDLE) {
			if (handle->evtTimeOut < time(NULL)) {
				DEBUG_DUMP("IntrRecv: time out:", handle->evtBuf, handle->evtBufLen);
				status = USBH_UNRECOVERED_ERROR;
				handle->outState = BT_EVT_GET_DATA;
			}
		} else if (urbState == USBH_URB_STALL) {
			USBH_DbgLog("IntrRecv: stall");
			if (USBH_ClrFeature(phost, handle->intrEp) == USBH_OK) {	// Change state to issue next IN token
				handle->evtState = BT_EVT_GET_DATA;
			}
		} else {
			USBH_ErrLog("IntrRecv: urbState=%d", urbState);
			status = USBH_UNRECOVERED_ERROR;
			handle->outState = BT_EVT_GET_DATA;
		}
		break;
	case BT_EVT_POST_WAIT:
		if (bt_putHCIevent(handle->evtBuf, handle->evtBufLen) == handle->evtBufLen) {
			handle->evtState = BT_EVT_GET_DATA;
		}
		break;
	default:
		handle->evtState = BT_EVT_IDLE;
		break;
	}
	return(status);
}
USBH_StatusTypeDef USBH_WCC_Bluetooth_SOFProcess(USBH_HandleTypeDef *phost){
	WCC_Bluetooth_HandleTypeDef *handle;

	handle = (WCC_Bluetooth_HandleTypeDef *)(phost->pActiveClass->pData);
	if (handle->evtState == BT_EVT_POLL) {
		if ((phost->Timer - handle->timer) >= handle->poll) {
//			USBH_DbgLog("IntrRecv: poll over f=%lu,%lu", handle->timer, phost->Timer);
			handle->evtState = BT_EVT_GET_DATA;
		}
	}
	return(USBH_OK);
}

USBH_StatusTypeDef USBH_WCC_Bluetooth_ProcessOut(USBH_HandleTypeDef *phost) {
	USBH_StatusTypeDef 			status;
	USBH_URBStateTypeDef		urbState;
	int							rc;
	WCC_Bluetooth_HandleTypeDef *handle;

	handle = (WCC_Bluetooth_HandleTypeDef *)(phost->pActiveClass->pData);
	status = USBH_OK;
	switch(handle->outState){
	case BT_OUT_IDLE:
		if (handle->outHcNum != 0) {
			handle->outState = BT_OUT_START;
		}
	    break;
	case BT_OUT_START:
		rc = bt_getACLout(handle->outBuf, sizeof(handle->outBuf));
		if (rc > 0) {
			handle->outBufLen = rc;
			handle->outState  = BT_OUT_POST;
		}
		break;
	case BT_OUT_POST:
		USBH_BulkSendData(phost, handle->outBuf, handle->outBufLen, handle->outHcNum, 1);
		handle->outTimeOut  = time(NULL) + USBH_WCC_BLUETOOTH_TIME_OUT;
		handle->outState    = BT_OUT_WAIT;
		break;
	case BT_OUT_WAIT:
		urbState = USBH_LL_GetURBState(phost, handle->outHcNum);
		if (urbState == USBH_URB_DONE) {
			DEBUG_DUMP("BulkSend:", handle->outBuf, handle->outBufLen);
			handle->outState = BT_OUT_START;
		} else if (urbState == USBH_URB_IDLE) {
			if (handle->outTimeOut < time(NULL)) {
				DEBUG_DUMP("BulkSend: time out:", handle->outBuf, handle->outBufLen);
				status = USBH_UNRECOVERED_ERROR;
				handle->outState = BT_OUT_IDLE;
			}
		} else if (urbState == USBH_URB_STALL) {
			if (handle->outTimeOut < time(NULL)) {
				/* Issue Clear Feature on interrupt IN endpoint */
				if (USBH_ClrFeature(phost, handle->outEp) == USBH_OK) {
					/* Change state to issue next IN token */
					DEBUG_DUMP("BulkSend: URB_STALL Clear OK", "", 0);
					handle->outState = BT_OUT_START;
				}
			} else {
				DEBUG_DUMP("BulkSend: URB_STALL failed the trying", "", 0);
				status = USBH_FAIL;
				handle->outState = BT_OUT_IDLE;
			}
		} else {
			USBH_ErrLog("BulkSend: URB_STATE=%d", urbState);
			status = USBH_UNRECOVERED_ERROR;
			handle->outState = BT_OUT_IDLE;
		}
		break;
	default:
		handle->outState = BT_OUT_IDLE;
		break;
	}
	return(status);
}

USBH_StatusTypeDef USBH_WCC_Bluetooth_ProcessIn(USBH_HandleTypeDef *phost) {
	USBH_StatusTypeDef 			status;
	USBH_URBStateTypeDef		urbState;
	WCC_Bluetooth_HandleTypeDef *handle;

	handle = (WCC_Bluetooth_HandleTypeDef *)(phost->pActiveClass->pData);

	status = USBH_OK;
	switch(handle->inState){
	case BT_IN_IDLE:
	case BT_IN_START:
		if (handle->inHcNum != 0) {
			handle->inState = BT_IN_POLL;
		}
	    break;
	case BT_IN_POLL:
		USBH_BulkReceiveData(phost, handle->inBuf, sizeof(handle->inBuf), handle->inHcNum);
		handle->inTimeOut = time(NULL) + USBH_WCC_BLUETOOTH_TIME_OUT;
		handle->inState   = BT_IN_POLL_WAIT;
		break;
	case BT_IN_POLL_WAIT:		// Wait at next frame boundery (true communication start timing)
		urbState = USBH_LL_GetURBState(phost, handle->inHcNum);
		switch(urbState){
		case USBH_URB_DONE:
			DEBUG_DUMP("BulkRecv:", handle->inBuf, handle->inBufLen);
			handle->inBufLen = USBH_LL_GetLastXferSize(phost, handle->inHcNum);
			handle->inState  = BT_IN_POST_WAIT;
			break;
		case USBH_URB_IDLE:
			if (handle->inTimeOut < time(NULL)) {
				handle->inState = BT_IN_IDLE;
			}
			break;
		case USBH_URB_STALL:
			if (handle->inTimeOut < time(NULL)) {
				/* Issue Clear Feature on interrupt IN endpoint */
				if (USBH_ClrFeature(phost, handle->inEp) == USBH_OK) {
					/* Change state to issue next IN token */
					DEBUG_DUMP("BulkRecv: URB_STALL Clear OK", "", 0);
					handle->inState = BT_IN_POLL;
				}
			} else {
				DEBUG_DUMP("BulkRecv: URB_STALL failed the trying", "", 0);
				status = USBH_FAIL;
				handle->inState = BT_IN_IDLE;
			}
			break;
		default:
			USBH_ErrLog("BulkRecv: URB_STATE=%d", urbState);
			status = USBH_UNRECOVERED_ERROR;
			handle->inState = BT_IN_IDLE;
			break;
		}
		break;
	case BT_IN_POST_WAIT:
		if (bt_putHCIin(handle->inBuf, handle->inBufLen) == handle->inBufLen) {
			handle->inState = BT_IN_POLL;
		}
		break;

	default:
		handle->inState = BT_IN_IDLE;
		break;
	}
	return(status);
}

USBH_StatusTypeDef USBH_WCC_Bluetooth_Process(USBH_HandleTypeDef *phost){
	USBH_StatusTypeDef	status;
	static int	r = 0;

	status = USBH_OK;
#if USBH_WCC_BLUETOOTH_TASK_SCHEDULE_2 == 1
	switch(r){
	case 0:
		status = USBH_WCC_Bluetooth_ProcessCmd(phost);
		break;
	case 1:
		status = USBH_WCC_Bluetooth_ProcessEvt(phost);
		break;
	case 2:
		status = USBH_WCC_Bluetooth_ProcessOut(phost);
		break;
	case 3:
		status = USBH_WCC_Bluetooth_ProcessIn(phost);
		break;
	case 4:
		bt_process();
		break;
	default:
		break;
	}
	if (++r > 4) {
		r = 0;
	}
#elif USBH_WCC_BLUETOOTH_TASK_SCHEDULE_2 == 2
	status = USBH_WCC_Bluetooth_ProcessCmd(phost);
	if ((status == USBH_OK) || (status == USBH_BUSY)) {
		status = USBH_WCC_Bluetooth_ProcessEvt(phost);
	}
	if ((status == USBH_OK) || (status == USBH_BUSY)) {
		status = USBH_WCC_Bluetooth_ProcessOut(phost);
	}
	if ((status == USBH_OK) || (status == USBH_BUSY)) {
		status = USBH_WCC_Bluetooth_ProcessIn(phost);
	}
	if ((status == USBH_OK) || (status == USBH_BUSY)) {
		bt_process();
	}
#elif USBH_WCC_BLUETOOTH_TASK_SCHEDULE_2 == 3
	switch(r){
	case 0:
		status = USBH_WCC_Bluetooth_ProcessCmd(phost);
		break;
	case 1:
		status = USBH_WCC_Bluetooth_ProcessEvt(phost);
		break;
	case 2:
		status = USBH_WCC_Bluetooth_ProcessOut(phost);
		break;
	case 3:
		status = USBH_WCC_Bluetooth_ProcessIn(phost);
		break;
	default:
		break;
	}
	bt_process();

	if (++r > 3) {
		r = 0;
	}
#endif
#ifdef DEBUG10
	{
		static int	i;

		if (i++ > 100000) {
			i = 0;
			printf("usbh_wcc_bluetooth_process hart beat\n");
		}
	}
#endif
	return(status);
}

// Callbacks jump table
USBH_ClassTypeDef  USBH_wcc_bluetooth ={
	"Bluetooth",
	USB_WCC_CLASS,
	USBH_WCC_Bluetooth_InterfaceInit,
	USBH_WCC_Bluetooth_InterfaceDeInit,
	USBH_WCC_Bluetooth_ClassRequest,
	USBH_WCC_Bluetooth_Process,
	USBH_WCC_Bluetooth_SOFProcess,
	NULL,
};

//------------------------------------------------------------------------------
