//
//  usbh_wcc_bluetooth.c
//
//  USB Host Protocol Stack - USB Wireless controller class for bluetooth dongle
//  Copyright (C) 2019 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 / STM32Cube FW_F4 V1.24.1
//
//	Device:
//		STM32Fxx
//
//	Update history
//	---------- ----- -------------------------------------------
//	2019.04.13 v0.0  First cording
//
#include <string.h>
#include "usbh_core.h"
#include "usbh_wcc_bluetooth.h"
#include "bt_hci.h"
#include "bt_process.h"

//#define DEBUG		0b0011							// Message dump switch: b3=in b2=out b1=evt b0=cmd
#define MIN(a,b)	((a) < (b) ? (a) : (b))
#define MAX(a,b)	((a) > (b) ? (a) : (b))

// DEBUG tools
#ifdef DEBUG
#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]); putchar('\n'); }
#else
#define DEBUGP(...)
#define DEBUG_DUMP(s,b,l)
#endif


//
//	Configure
//
#define USBH_WCC_BLUETOOTH_INTR_POLL_MIN	3		// Minimum poling wait time (USB frame count)
#define USBH_WCC_BLUETOOTH_CMD_WAIT_TIME	2		// [ms] Busy retry interval
#define USBH_WCC_BLUETOOTH_CMD_TIME_OUT		200		// [ms] Fail safe
#define USBH_WCC_BLUETOOTH_OUT_TIME_OUT		100		// [ms] Fail safe
#define USBH_WCC_BLUETOOTH_IN_POLL_TIME		1000	// [ms]

//
//  Control block table
//
typedef enum {
	BT_STATE_INIT = 0,
	BT_STATE_POLL,
	BT_STATE_SEND_COMMAND,
	BT_STATE_SEND_WAIT,
	BT_STATE_POST_EVENT,
}	BT_StateTypeDef;

typedef enum {
	BT_OUT_IDLE = 0,
	BT_OUT_START,
	BT_OUT_POST,
	BT_OUT_WAIT,
}	BT_OutStateTypeDef;

typedef enum {
	BT_IN_IDLE = 0,
	BT_IN_START,
	BT_IN_POLL,
	BT_IN_POLL_WAIT,
	BT_IN_GET,
	BT_IN_POST_WAIT,
} BT_InStateTypeDef;

typedef struct USBH_WCC_Bluetooth_CB {	//
	uint8_t			intrHcNum;			//
	uint8_t			outHcNum;			//
	uint8_t			inHcNum;			//
	uint8_t			intrEp;				//
	uint8_t			outEp;				//
	uint8_t			inEp;				//
	uint16_t		intrEpSize;			//
	uint16_t		outEpSize;			//
	uint16_t		inEpSize;			//
	BT_StateTypeDef	state;				//
	BT_OutStateTypeDef	outState;		//
	BT_InStateTypeDef	inState;		//
	uint32_t		cmdWaitTime;		//
	uint32_t		cmdTimeOut;			//
	uint32_t		outTimeOut;			//
	uint32_t		inTimeOut;			//
	uint32_t		poll;				//
	uint32_t		timer;				//
	int				evtFlag;			//
	size_t			cmdBufLen;			//
	size_t			evtBufLen;			//
	size_t			outBufLen;			//
	size_t			inBufLen;			//
	unsigned char	cmdBuf[64];			// USB endpoint buffer
	unsigned char	evtBuf[16];			//
	unsigned char	outBuf[64];			//
	unsigned char	inBuf[64];			//
}	WCC_Bluetooth_HandleTypeDef;		//

//------------------------------------------------------------------------------
//	USB pluged in
//------------------------------------------------------------------------------
static USBH_StatusTypeDef 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;

		DEBUGP("USBH_WCC_Bluetooth_InterfaceInit: interface=%d\n", interface);
		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);
			DEBUGP("USBH_WCC_Bluetooth_InterfaceInit: bEndpointAddress=0x%02x,wMaxPacketSize=%d,h=%d\n", a, p, h);
			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;
			case 0x03:					// Voice channels OUT
			case 0x83:					// Voice channels IN
				DEBUGP("USBH_WCC_Bluetooth_InterfaceInit: Skip Voice\n");
				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
//------------------------------------------------------------------------------
static USBH_StatusTypeDef interfaceDeInit (USBH_HandleTypeDef *phost){
	WCC_Bluetooth_HandleTypeDef *handle;

	bt_setDeadHCI();					// To L2CAP communication path
	bt_deinit();

	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;
	}
	USBH_DbgLog("USBH_WCC_Bluetooth_InterfaceDeInit: OK");
	return(USBH_OK);
}

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

//------------------------------------------------------------------------------
//	Process
//------------------------------------------------------------------------------

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

	handle = (WCC_Bluetooth_HandleTypeDef *)(phost->pActiveClass->pData);
	status = USBH_OK;
	switch(handle->state){
	case BT_STATE_INIT:
	    if ((phost->Timer & 1U) == 0U) {
			status = USBH_InterruptReceiveData(phost, handle->evtBuf, MIN(handle->intrEpSize, sizeof(handle->evtBuf)), handle->intrHcNum);
			handle->timer   = phost->Timer;
			handle->evtFlag = 0;
			handle->state   = BT_STATE_POLL;
			if (status != USBH_OK) {
				USBH_ErrLog("IntrRecv: USBH_InterruptReceiveData()=%d", status);
			}
	    }
		break;
	case BT_STATE_POLL:
		rc = bt_getHCIcommand(handle->cmdBuf, sizeof(handle->cmdBuf));
		if (rc > 0) {
			handle->cmdBufLen  = rc;
			handle->cmdTimeOut = HAL_GetTick() + USBH_WCC_BLUETOOTH_CMD_TIME_OUT;
			handle->state      = BT_STATE_SEND_COMMAND;
			DEBUGP("CtrlSend: start %d bytes\n", rc);

		} else if ((urbState = USBH_LL_GetURBState(phost, handle->intrHcNum)) == USBH_URB_DONE) {
			if (handle->evtFlag == 0) {
				handle->evtFlag   = 1;
				handle->evtBufLen = USBH_LL_GetLastXferSize(phost, handle->intrHcNum);
				handle->state     = BT_STATE_POST_EVENT;
#if (DEBUG & 2)
DEBUG_DUMP("IntrRecv:", handle->evtBuf, handle->evtBufLen);
#endif
			}
		} else if (urbState != USBH_URB_IDLE){
			USBH_ErrLog("IntrRecv: error urbState=%d", urbState);
		}
		if ((phost->Timer - handle->timer) >= handle->poll) {
			status = USBH_InterruptReceiveData(phost, handle->evtBuf, MIN(handle->intrEpSize, sizeof(handle->evtBuf)), handle->intrHcNum);
			if (status != USBH_OK) {
				USBH_ErrLog("IntrRecv: USBH_InterruptReceiveData()=%d", status);
			}
			handle->timer   = phost->Timer;
			handle->evtFlag = 0;
		}
		break;
	case BT_STATE_SEND_COMMAND:
		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) {
#if (DEBUG & 1)
DEBUG_DUMP("CtrlSend:", handle->cmdBuf, handle->cmdBufLen);
#endif
			handle->state = BT_STATE_POLL;
		} else if (status == USBH_BUSY) {
			handle->state = BT_STATE_SEND_WAIT;
			handle->cmdWaitTime = HAL_GetTick() + USBH_WCC_BLUETOOTH_CMD_WAIT_TIME;
//			DEBUGP("CtrlSend: busy %lu\n", HAL_GetTick());
		} else {
			USBH_ErrLog("CtrlSend: USBH_CtlReq()=%d", status);
			handle->state = BT_STATE_POLL;
		}
		if (handle->cmdTimeOut < HAL_GetTick()) {
			USBH_ErrLog("CtrlSend: USBH_CtlReq() Timeout");
			handle->state = BT_STATE_POLL;
		}
		break;
	case BT_STATE_SEND_WAIT:
		if (handle->cmdWaitTime < HAL_GetTick()) {
			handle->state = BT_STATE_SEND_COMMAND;
		}
		break;
	case BT_STATE_POST_EVENT:
		if (bt_putHCIevent(handle->evtBuf, handle->evtBufLen) == handle->evtBufLen) {
			handle->state = BT_STATE_POLL;
		}
		break;
	default:
		break;
	}
	return(status);
}

static USBH_StatusTypeDef sofProcess(USBH_HandleTypeDef *phost){
	return(USBH_OK);
}

static USBH_StatusTypeDef outProcess(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  = HAL_GetTick() + USBH_WCC_BLUETOOTH_OUT_TIME_OUT;
		handle->outState    = BT_OUT_WAIT;
		break;
	case BT_OUT_WAIT:
		urbState = USBH_LL_GetURBState(phost, handle->outHcNum);
		if (urbState == USBH_URB_DONE) {
#if (DEBUG & 4)
			DEBUG_DUMP("BulkSend:", handle->outBuf, handle->outBufLen);
#endif
			handle->outState = BT_OUT_START;
		} else if (urbState == USBH_URB_IDLE) {
			if (handle->outTimeOut < HAL_GetTick()) {
				USBH_DbgLog("BulkSend: time out:");
				status = USBH_UNRECOVERED_ERROR;
				handle->outState = BT_OUT_IDLE;
			}
		} else if (urbState == USBH_URB_STALL) {
			if (handle->outTimeOut < HAL_GetTick()) {
				/* Issue Clear Feature on interrupt IN endpoint */
				if (USBH_ClrFeature(phost, handle->outEp) == USBH_OK) {
					/* Change state to issue next IN token */
					USBH_ErrLog("BulkSend: URB_STALL Clear OK");
					handle->outState = BT_OUT_START;
				} else {
					USBH_ErrLog("BulkSend: URB_STALL Clear NG");
				}
			} else {
				USBH_ErrLog("BulkSend: URB_STALL failed the trying");
				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);
}

static USBH_StatusTypeDef inProcess(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 = HAL_GetTick() + USBH_WCC_BLUETOOTH_IN_POLL_TIME;
		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:
			handle->inBufLen = USBH_LL_GetLastXferSize(phost, handle->inHcNum);
			handle->inState  = BT_IN_POST_WAIT;
#if (DEBUG & 8)
			DEBUG_DUMP("BulkRecv:", handle->inBuf, handle->inBufLen);
#endif
			break;
		case USBH_URB_IDLE:
			if (handle->inTimeOut < HAL_GetTick()) {
				handle->inState = BT_IN_IDLE;
			}
			break;
		case USBH_URB_STALL:
			if (handle->inTimeOut < HAL_GetTick()) {
				/* Issue Clear Feature on interrupt IN endpoint */
				if (USBH_ClrFeature(phost, handle->inEp) == USBH_OK) {
					/* Change state to issue next IN token */
					USBH_ErrLog("BulkRecv: URB_STALL Clear OK");
					handle->inState = BT_IN_POLL;
				}
			} else {
				USBH_ErrLog("BulkRecv: URB_STALL failed the trying");
				status = USBH_FAIL;
				handle->inState = BT_IN_IDLE;
			}
			break;
		case USBH_URB_NOTREADY:
//			DEBUGP("BulkRecv: USBH_URB_NOTREADY\n");
//			USBH_ErrLog("BulkRecv: USBH_URB_NOTREADY");
			break;
		default:
			if (urbState != USBH_URB_NOTREADY) {
				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);
}

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

	status = USBH_OK;

	switch(r){
	case 0:
		status = controlProcess(phost);
		break;
	case 1:
		status = outProcess(phost);
		break;
	case 2:
		status = inProcess(phost);
		break;
	default:
		break;
	}
	bt_process();

	if (++r > 2) {
		r = 0;
	}
	return(status);
}

// Callbacks jump table
USBH_ClassTypeDef  USBH_wcc_bluetooth ={
	"Bluetooth",
	USB_WCC_CLASS,
	interfaceInit,
	interfaceDeInit,
	classRequest,
	process,
	sofProcess,
	NULL,
};

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