/*
    ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio
              Copyright (C) 2015 Diego Ismirlian, TISA, (dismirlian (at) google's mail)

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

#ifndef USBH_H_
#define USBH_H_

#include "hal.h"

#ifndef HAL_USE_USBH
#define HAL_USE_USBH FALSE
#endif

#ifndef HAL_USBH_USE_FTDI
#define HAL_USBH_USE_FTDI FALSE
#endif

#ifndef HAL_USBH_USE_HUB
#define HAL_USBH_USE_HUB FALSE
#endif

#ifndef HAL_USBH_USE_MSD
#define HAL_USBH_USE_MSD FALSE
#endif

#ifndef HAL_USBH_USE_UVC
#define HAL_USBH_USE_UVC FALSE
#endif

#if HAL_USE_USBH

#include "osal.h"
#include "usbh/list.h"
#include "usbh/defs.h"

/* TODO:
 *
 * - Integrate VBUS power switching functionality to the API.
 *
 */


/*===========================================================================*/
/* Derived constants and error checks.                                       */
/*===========================================================================*/

#if !HAL_USBH_USE_HUB
#define USBH_MAX_ADDRESSES				1
#else
#define USBH_MAX_ADDRESSES				(HAL_USBHHUB_MAX_PORTS + 1)
#endif

enum usbh_status {
	USBH_STATUS_STOPPED = 0,
	USBH_STATUS_STARTED,
	USBH_STATUS_SUSPENDED,
};

enum usbh_devstatus {
	USBH_DEVSTATUS_DISCONNECTED = 0,
	USBH_DEVSTATUS_ATTACHED,
	USBH_DEVSTATUS_CONNECTED,
	USBH_DEVSTATUS_DEFAULT,
	USBH_DEVSTATUS_ADDRESS,
	USBH_DEVSTATUS_CONFIGURED,
};

enum usbh_devspeed {
	USBH_DEVSPEED_LOW = 0,
	USBH_DEVSPEED_FULL,
	USBH_DEVSPEED_HIGH,
};

enum usbh_epdir {
	USBH_EPDIR_IN		= 0x80,
	USBH_EPDIR_OUT		= 0
};

enum usbh_eptype {
	USBH_EPTYPE_CTRL	= 0,
	USBH_EPTYPE_ISO		= 1,
	USBH_EPTYPE_BULK	= 2,
	USBH_EPTYPE_INT		= 3,
};

enum usbh_epstatus {
	USBH_EPSTATUS_UNINITIALIZED = 0,
	USBH_EPSTATUS_CLOSED,
	USBH_EPSTATUS_OPEN,
	USBH_EPSTATUS_HALTED,
};

enum usbh_urbstatus {
	USBH_URBSTATUS_UNINITIALIZED = 0,
	USBH_URBSTATUS_INITIALIZED,
	USBH_URBSTATUS_PENDING,
//	USBH_URBSTATUS_QUEUED,
	USBH_URBSTATUS_ERROR,
	USBH_URBSTATUS_TIMEOUT,
	USBH_URBSTATUS_CANCELLED,
	USBH_URBSTATUS_STALL,
	USBH_URBSTATUS_DISCONNECTED,
//	USBH_URBSTATUS_EPCLOSED,
	USBH_URBSTATUS_OK,
};

/*===========================================================================*/
/* Driver data structures and types.                                         */
/*===========================================================================*/

/* forward declarations */
typedef struct USBHDriver USBHDriver;
typedef struct usbh_port usbh_port_t;
typedef	struct usbh_device usbh_device_t;
typedef struct usbh_ep usbh_ep_t;
typedef struct usbh_urb usbh_urb_t;
typedef struct usbh_baseclassdriver usbh_baseclassdriver_t;
typedef struct usbh_classdriverinfo usbh_classdriverinfo_t;
#if HAL_USBH_USE_HUB
typedef struct USBHHubDriver USBHHubDriver;
#endif

/* typedefs */
typedef enum usbh_status usbh_status_t;
typedef enum usbh_devspeed usbh_devspeed_t;
typedef enum usbh_devstatus usbh_devstatus_t;
typedef enum usbh_epdir usbh_epdir_t;
typedef enum usbh_eptype usbh_eptype_t;
typedef enum usbh_epstatus usbh_epstatus_t;
typedef enum usbh_urbstatus usbh_urbstatus_t;
typedef uint16_t usbh_portstatus_t;
typedef uint16_t usbh_portcstatus_t;
typedef void (*usbh_completion_cb)(usbh_urb_t *);

/* include the low level driver; the required definitions are above */
#include "hal_usbh_lld.h"

#define USBH_DEFINE_BUFFER(type, name)	USBH_LLD_DEFINE_BUFFER(type, name)

struct usbh_urb {
	usbh_ep_t *ep;

	void *userData;
	usbh_completion_cb callback;

	const void *setup_buff;
	void *buff;
	uint32_t requestedLength;
	uint32_t actualLength;

	usbh_urbstatus_t status;

	thread_reference_t waitingThread;
	thread_reference_t abortingThread;

	/* Low level part */
	_usbh_urb_ll_data
};

struct usbh_ep {
	usbh_device_t		*device;
	usbh_ep_t	 		*next;

	usbh_epstatus_t		status;
	uint8_t 			address;
	bool				in;
	usbh_eptype_t		type;
	uint16_t			wMaxPacketSize;
	uint8_t				bInterval;

	/* debug */
	const char			*name;

	/* Low-level part */
	_usbh_ep_ll_data
};

struct usbh_device {
	USBHDriver *host;	/* shortcut to host */

	usbh_ep_t ctrl;
	usbh_ep_t *endpoints;

	usbh_baseclassdriver_t *drivers;

	uint16_t langID0;

	usbh_devstatus_t status;
	usbh_devspeed_t speed;

	USBH_DEFINE_BUFFER(usbh_device_descriptor_t, devDesc);
	unsigned char align_bytes[2];
	USBH_DEFINE_BUFFER(usbh_config_descriptor_t, basicConfigDesc);

	uint8_t *fullConfigurationDescriptor;
	uint8_t keepFullCfgDesc;

	uint8_t address;
	uint8_t bConfiguration;

	/* Low level part */
	_usbh_device_ll_data
};


struct usbh_port {
#if HAL_USBH_USE_HUB
	USBHHubDriver *hub;
#endif

	usbh_portstatus_t status;
	usbh_portcstatus_t c_status;

	usbh_port_t *next;

	uint8_t number;

	usbh_device_t device;

	/* Low level part */
	_usbh_port_ll_data
};

struct USBHDriver {
	usbh_status_t status;
	uint8_t address_bitmap[(USBH_MAX_ADDRESSES + 7) / 8];

	usbh_port_t rootport;

#if HAL_USBH_USE_HUB
	struct list_head hubs;
#endif

	/* Low level part */
	_usbhdriver_ll_data

#if USBH_DEBUG_ENABLE
	/* debug */
	uint8_t dbg_buff[USBH_DEBUG_BUFFER];
	THD_WORKING_AREA(waDebug, 512);
	input_queue_t iq;
#endif
};



/*===========================================================================*/
/* External declarations.                                                    */
/*===========================================================================*/

#if STM32_USBH_USE_OTG1
extern USBHDriver USBHD1;
#endif

#if STM32_USBH_USE_OTG2
extern USBHDriver USBHD2;
#endif


/*===========================================================================*/
/* Main driver API.		                                                     */
/*===========================================================================*/

#ifdef __cplusplus
extern "C" {
#endif

	/* Main functions */
	void usbhObjectInit(USBHDriver *usbh);
	void usbhInit(void);
	void usbhStart(USBHDriver *usbh);
	void usbhStop(USBHDriver *usbh);
	void usbhSuspend(USBHDriver *usbh);
	void usbhResume(USBHDriver *usbh);

	/* Device-related */
#if	USBH_DEBUG_ENABLE && USBH_DEBUG_ENABLE_INFO
	void usbhDevicePrintInfo(usbh_device_t *dev);
	void usbhDevicePrintConfiguration(const uint8_t *descriptor, uint16_t rem);
#else
#	define usbhDevicePrintInfo(dev) do {} while(0)
#	define usbhDevicePrintConfiguration(descriptor, rem) do {} while(0)
#endif
	bool usbhDeviceReadString(usbh_device_t *dev, char *dest, uint8_t size,
			uint8_t index, uint16_t langID);
	static inline usbh_port_t *usbhDeviceGetPort(usbh_device_t *dev) {
		return container_of(dev, usbh_port_t, device);
	}

	/* Synchronous API */
	usbh_urbstatus_t usbhBulkTransfer(usbh_ep_t *ep,
			void *data,
			uint32_t len,
			uint32_t *actual_len,
			systime_t timeout);
	usbh_urbstatus_t usbhControlRequest(usbh_device_t *dev,
			uint8_t bmRequestType,
			uint8_t bRequest,
			uint16_t wValue,
			uint16_t wIndex,
			uint16_t wLength,
			uint8_t *buff);
	usbh_urbstatus_t usbhControlRequestExtended(usbh_device_t *dev,
			const usbh_control_request_t *req,
			uint8_t *buff,
			uint32_t *actual_len,
			systime_t timeout);

	/* Standard request helpers */
	bool usbhStdReqGetDeviceDescriptor(usbh_device_t *dev,
			uint16_t wLength,
			uint8_t *buf);
	bool usbhStdReqGetConfigurationDescriptor(usbh_device_t *dev,
			uint8_t index,
			uint16_t wLength,
			uint8_t *buf);
	bool usbhStdReqGetStringDescriptor(usbh_device_t *dev,
			uint8_t index,
			uint16_t langID,
			uint16_t wLength,
			uint8_t *buf);
	bool usbhStdReqSetInterface(usbh_device_t *dev,
			uint8_t bInterfaceNumber,
			uint8_t bAlternateSetting);
	bool usbhStdReqGetInterface(usbh_device_t *dev,
			uint8_t bInterfaceNumber,
			uint8_t *bAlternateSetting);

	/* Endpoint/pipe management */
	void usbhEPObjectInit(usbh_ep_t *ep, usbh_device_t *dev, const usbh_endpoint_descriptor_t *desc);
	static inline void usbhEPOpen(usbh_ep_t *ep) {
		osalDbgCheck(ep != 0);
		osalSysLock();
		osalDbgAssert(ep->status == USBH_EPSTATUS_CLOSED, "invalid state");
		usbh_lld_ep_open(ep);
		ep->next = ep->device->endpoints;
		ep->device->endpoints = ep;
		osalSysUnlock();
	}
	static inline void usbhEPCloseS(usbh_ep_t *ep) {
		osalDbgCheck(ep != 0);
		osalDbgCheckClassS();
		osalDbgAssert(ep->status != USBH_EPSTATUS_UNINITIALIZED, "invalid state");
		if (ep->status == USBH_EPSTATUS_CLOSED) {
			osalOsRescheduleS();
			return;
		}
		usbh_lld_ep_close(ep);
	}
	static inline void usbhEPClose(usbh_ep_t *ep) {
		osalSysLock();
		usbhEPCloseS(ep);
		osalSysUnlock();
	}
	static inline void usbhEPResetI(usbh_ep_t *ep) {
		osalDbgCheckClassI();
		osalDbgCheck(ep != NULL);
		usbh_lld_epreset(ep);
	}
	static inline bool usbhEPIsPeriodic(usbh_ep_t *ep) {
		osalDbgCheck(ep != NULL);
		return (ep->type & 1) != 0;
	}
	static inline bool usbhURBIsBusy(usbh_urb_t *urb) {
		osalDbgCheck(urb != NULL);
		return (urb->status == USBH_URBSTATUS_PENDING);
	}
	static inline void usbhEPSetName(usbh_ep_t *ep, const char *name) {
		ep->name = name;
	}

	/* URB management */
	void usbhURBObjectInit(usbh_urb_t *urb, usbh_ep_t *ep, usbh_completion_cb callback,
			void *user, void *buff, uint32_t len);
	void usbhURBObjectResetI(usbh_urb_t *urb);
	void usbhURBSubmitI(usbh_urb_t *urb);
	bool usbhURBCancelI(usbh_urb_t *urb);
	msg_t usbhURBSubmitAndWaitS(usbh_urb_t *urb, systime_t timeout);
	void usbhURBCancelAndWaitS(usbh_urb_t *urb);
	msg_t usbhURBWaitTimeoutS(usbh_urb_t *urb, systime_t timeout);

	/* Main loop */
	void usbhMainLoop(USBHDriver *usbh);

#ifdef __cplusplus
}
#endif


/*===========================================================================*/
/* Class driver definitions and API.                                         */
/*===========================================================================*/

typedef struct usbh_classdriver_vmt usbh_classdriver_vmt_t;
struct usbh_classdriver_vmt {
	usbh_baseclassdriver_t *(*load)(usbh_device_t *dev,	const uint8_t *descriptor, uint16_t rem);
	void (*unload)(usbh_baseclassdriver_t *drv);
};

struct usbh_classdriverinfo {
	int16_t class;
	int16_t subclass;
	int16_t protocol;
	const char *name;
	const usbh_classdriver_vmt_t *vmt;
};

#define _usbh_base_classdriver_data		\
	const usbh_classdriverinfo_t *info;	\
	usbh_device_t *dev;					\
	usbh_baseclassdriver_t *next;

struct usbh_baseclassdriver {
	_usbh_base_classdriver_data
};


/*===========================================================================*/
/* Helper functions.				                                         */
/*===========================================================================*/
#include <usbh/desciter.h>	/* descriptor iterators */
#include <usbh/debug.h>		/* debug */

#endif

#endif /* USBH_H_ */
