From 7acc6fe474766788687a5257be21ac549bed77f3 Mon Sep 17 00:00:00 2001
From: Amir Hammad <amir.hammad@hotmail.com>
Date: Wed, 1 Apr 2015 16:22:05 +0200
Subject: libusbhost: Open source USB host stack for embedded devices

First public version, date: 1.4.2015

Signed-off-by: Amir Hammad <amir.hammad@hotmail.com>
---
 src/demo.c                    |  186 ++++++++
 src/usart_helpers.c           |  287 +++++++++++
 src/usart_helpers.h           |   56 +++
 src/usbh_driver_gp_xbox.c     |  420 +++++++++++++++++
 src/usbh_driver_hid_mouse.c   |  294 ++++++++++++
 src/usbh_driver_hub.c         |  865 ++++++++++++++++++++++++++++++++++
 src/usbh_driver_hub_private.h |  108 +++++
 src/usbh_hubbed.c             |  634 +++++++++++++++++++++++++
 src/usbh_lld_stm32f4.c        | 1048 +++++++++++++++++++++++++++++++++++++++++
 9 files changed, 3898 insertions(+)
 create mode 100644 src/demo.c
 create mode 100644 src/usart_helpers.c
 create mode 100644 src/usart_helpers.h
 create mode 100644 src/usbh_driver_gp_xbox.c
 create mode 100644 src/usbh_driver_hid_mouse.c
 create mode 100644 src/usbh_driver_hub.c
 create mode 100644 src/usbh_driver_hub_private.h
 create mode 100644 src/usbh_hubbed.c
 create mode 100644 src/usbh_lld_stm32f4.c

(limited to 'src')

diff --git a/src/demo.c b/src/demo.c
new file mode 100644
index 0000000..f4ef456
--- /dev/null
+++ b/src/demo.c
@@ -0,0 +1,186 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "usart_helpers.h"			/// provides LOG_PRINTF macros used for debugging
+#include "usbh_hubbed.h"			/// provides usbh_init() and usbh_poll()
+#include "usbh_lld_stm32f4.h"		/// provides low level usb host driver for stm32f4 platform
+#include "usbh_driver_hid_mouse.h"	/// provides usb device driver Human Interface Device - type mouse
+#include "usbh_driver_hub.h"		/// provides usb full speed hub driver (Low speed devices on hub are not supported)
+#include "usbh_driver_gp_xbox.h"	/// provides usb device driver for Gamepad: Microsoft XBOX compatible Controller
+
+ // STM32f407 compatible
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/stm32/usart.h>
+#include <libopencm3/stm32/timer.h>
+#include <libopencm3/stm32/otg_hs.h>
+#include <libopencm3/stm32/otg_fs.h>
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+static inline void delay_ms_busy_loop(uint32_t ms)
+{
+	volatile uint32_t i;
+	for (i = 0; i < 14903*ms; i++);
+}
+
+
+/* Set STM32 to 168 MHz. */
+static void clock_setup(void)
+{
+	rcc_clock_setup_hse_3v3(&hse_8mhz_3v3[CLOCK_3V3_168MHZ]);
+
+	// GPIO
+	rcc_peripheral_enable_clock(&RCC_AHB1ENR, RCC_AHB1ENR_IOPAEN);	// OTG_FS + button
+	rcc_peripheral_enable_clock(&RCC_AHB1ENR, RCC_AHB1ENR_IOPBEN);	// OTG_HS
+	rcc_peripheral_enable_clock(&RCC_AHB1ENR, RCC_AHB1ENR_IOPCEN);	// USART + OTG_FS charge pump
+	rcc_peripheral_enable_clock(&RCC_AHB1ENR, RCC_AHB1ENR_IOPDEN);	// LEDS
+
+	// periphery
+	rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_USART6EN);// USART
+	rcc_peripheral_enable_clock(&RCC_AHB2ENR, RCC_AHB2ENR_OTGFSEN);
+	rcc_peripheral_enable_clock(&RCC_AHB1ENR, RCC_AHB1ENR_OTGHSEN);
+}
+
+static void gpio_setup(void)
+{
+	/* Set GPIO12-15 (in GPIO port D) to 'output push-pull'. */
+	gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT,
+			GPIO_PUPD_NONE, GPIO12 | GPIO13 | GPIO14 | GPIO15);
+
+	/* Set	 */
+	gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO0);
+	gpio_clear(GPIOC, GPIO0);
+
+	// OTG_FS
+	gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO11 | GPIO12);
+	gpio_set_af(GPIOA, GPIO_AF10, GPIO11 | GPIO12);
+
+	// OTG_HS
+	gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO15 | GPIO14);
+	gpio_set_af(GPIOB, GPIO_AF12, GPIO14 | GPIO15);
+
+	// USART TX
+	gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO6 | GPIO7);
+	gpio_set_af(GPIOC, GPIO_AF8, GPIO6 | GPIO7);
+
+	// button
+	gpio_mode_setup(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO0);
+}
+
+static const usbh_dev_driver_t *device_drivers[] = {
+	&usbh_hub_driver,
+	&usbh_hid_mouse_driver,
+	&usbh_gp_xbox_driver,
+	0
+};
+
+static void gp_xbox_update(uint8_t device_id, gp_xbox_packet_t packet)
+{
+	(void)device_id;
+	(void)packet;
+	LOG_PRINTF("update %d: %d %d \r\n", device_id, packet.axis_left_x, packet.buttons & GP_XBOX_BUTTON_A);
+}
+
+
+static void gp_xbox_connected(uint8_t device_id)
+{
+	(void)device_id;
+	LOG_PRINTF("connected %d", device_id);
+}
+
+static void gp_xbox_disconnected(uint8_t device_id)
+{
+	(void)device_id;
+	LOG_PRINTF("disconnected %d", device_id);
+}
+
+static const gp_xbox_config_t gp_xbox_config = {
+	.update = &gp_xbox_update,
+	.notify_connected = &gp_xbox_connected,
+	.notify_disconnected = &gp_xbox_disconnected
+};
+
+static void mouse_in_message_handler(uint8_t device_id, const uint8_t *data)
+{
+	(void)device_id;
+	(void)data;
+	// print only first 4 bytes, since every mouse should have at least these four set.
+	// Report descriptors are not read by driver for now, so we do not know what each byte means
+	LOG_PRINTF("MOUSE EVENT %02X %02X %02X %02X \r\n", data[0], data[1], data[2], data[3]);
+}
+
+static const hid_mouse_config_t mouse_config = {
+	.mouse_in_message_handler = &mouse_in_message_handler
+};
+
+int main(void)
+{
+	clock_setup();
+	gpio_setup();
+
+#ifdef USART_DEBUG
+	usart_init(USART6, 921600);
+#endif
+	LOG_PRINTF("\r\n\r\n\r\n\r\n\r\n###################\r\nInit\r\n");
+
+	/**
+	 * device driver initialization
+	 *
+	 * Pass configuration struct where the callbacks are defined
+	 */
+	hid_mouse_driver_init(&mouse_config);
+	hub_driver_init();
+	gp_xbox_driver_init(&gp_xbox_config);
+
+	gpio_set(GPIOD,  GPIO13);
+
+	/**
+	 * Pass array of supported low level drivers
+	 * In case of stm32f407, there are up to two supported OTG hosts on one chip.
+	 * Each one can be enabled or disabled in config.mk - optimization for speed
+	 *
+	 * Pass array of supported device drivers
+	 */
+	usbh_init(usbh_lld_stm32f4_drivers, device_drivers);
+	gpio_clear(GPIOD,  GPIO13);
+
+	LOG_PRINTF("USB init complete\r\n");
+
+	uint32_t i = 0;
+
+	while (1) {
+		LOG_FLUSH();
+
+		// Toggle some led
+		gpio_set(GPIOD,  GPIO14);
+		usbh_poll(i);
+		gpio_clear(GPIOD,  GPIO14);
+
+		delay_ms_busy_loop(1);
+		i += 1000;
+	}
+
+	return 0;
+}
diff --git a/src/usart_helpers.c b/src/usart_helpers.c
new file mode 100644
index 0000000..31a7556
--- /dev/null
+++ b/src/usart_helpers.c
@@ -0,0 +1,287 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include "usart_helpers.h"
+
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <libopencm3/stm32/usart.h>
+#include <libopencm3/stm32/gpio.h>
+
+
+#ifndef USART_DEBUG
+
+void usart_init(uint32_t usart, uint32_t baudrate)
+{
+	(void)usart;
+	(void)baudrate;
+}
+void usart_printf(const char *str, ...)
+{
+	(void)str;
+}
+void usart_vprintf(const char *str, va_list va)
+{
+	(void)va;
+	(void)str;
+}
+void usart_fifo_send(void){}
+
+void usart_call_cmd(struct usart_commands * commands)
+{
+	(void)commands;
+}
+void usart_interrupt(void){}
+
+#else
+#warning compiling with debug functions
+
+#define USART_FIFO_OUT_SIZE (4096)
+uint8_t usart_fifo_out_data[USART_FIFO_OUT_SIZE];
+uint32_t usart_fifo_out_len = 0;
+uint32_t usart_fifo_out_index = 0;
+
+#define USART_FIFO_IN_SIZE (1024)
+uint8_t usart_fifo_in_data[USART_FIFO_IN_SIZE];
+uint32_t usart_fifo_in_len = 0;
+uint32_t usart_fifo_in_index = 0;
+
+static uint32_t usart = 0;
+
+static uint8_t usart_fifo_pop(void)
+{
+	uint8_t ret;
+	usart_fifo_out_len--;
+	ret = usart_fifo_out_data[usart_fifo_out_index];
+	usart_fifo_out_index++;
+	if (usart_fifo_out_index == USART_FIFO_OUT_SIZE ) {
+		usart_fifo_out_index = 0;
+	}
+	return ret;
+}
+
+static void usart_fifo_push(uint8_t aData)
+{
+	uint32_t i;
+	if( (usart_fifo_out_len + 1) == USART_FIFO_OUT_SIZE)//overflow
+	{
+		usart_fifo_out_len = 0;
+		LOG_PRINTF("OVERFLOW!");
+		return;
+	}
+
+	i = usart_fifo_out_index + usart_fifo_out_len;
+	if (i >= USART_FIFO_OUT_SIZE) {
+		i -= USART_FIFO_OUT_SIZE;
+	}
+	usart_fifo_out_data[i] = aData;
+	usart_fifo_out_len++;
+}
+
+
+static uint8_t usart_fifo_in_pop(void)
+{
+	uint8_t ret;
+	usart_fifo_in_len--;
+	ret = usart_fifo_in_data[usart_fifo_in_index];
+	usart_fifo_in_index++;
+	if (usart_fifo_in_index == USART_FIFO_IN_SIZE ) {
+		usart_fifo_in_index = 0;
+	}
+	return ret;
+}
+
+static void usart_fifo_in_push(uint8_t aData)
+{
+	uint32_t i;
+	if( (usart_fifo_in_len + 1) == USART_FIFO_IN_SIZE)//overflow
+	{
+		usart_fifo_in_len = 0;
+		return;
+	}
+
+	i = usart_fifo_in_index + usart_fifo_in_len;
+	if (i >= USART_FIFO_IN_SIZE) {
+		i -= USART_FIFO_IN_SIZE;
+	}
+	usart_fifo_in_data[i] = aData;
+	usart_fifo_in_len++;
+}
+
+
+static void usart_write(const char * data, uint32_t len)
+{
+    uint32_t i;
+    for(i = 0; i < len; i++)
+    {
+        usart_fifo_push(data[i]);
+    }
+}
+void usart_printf(const char *str, ...)
+{
+	va_list va;
+	va_start(va, str);
+	usart_vprintf(str, va);
+	va_end(va);
+
+}
+
+void usart_vprintf(const char *str, va_list va)
+{
+	char databuffer[128];
+	int i = vsnprintf(databuffer, 128, str, va);
+	if (i > 0) {
+		usart_write(databuffer, i);
+	}
+}
+
+
+
+void usart_init(uint32_t arg_usart, uint32_t baudrate)
+{
+	usart_set_baudrate(arg_usart, baudrate);
+	usart_set_databits(arg_usart, 8);
+	usart_set_flow_control(arg_usart, USART_FLOWCONTROL_NONE);
+	usart_set_mode(arg_usart, USART_MODE_TX | USART_MODE_RX);
+	usart_set_parity(arg_usart, USART_PARITY_NONE);
+	usart_set_stopbits(arg_usart, USART_STOPBITS_1);
+
+	usart_enable_rx_interrupt(arg_usart);
+	usart_enable(arg_usart);
+	usart = arg_usart;
+}
+void usart_interrupt(void)
+{
+	if (usart_get_interrupt_source(usart, USART_SR_RXNE)) {
+		uint8_t data = usart_recv(usart);
+		usart_fifo_in_push(data);
+		if ( data != 3 && data != '\r' && data != '\n') {
+			usart_fifo_push(data);
+		} else {
+			LOG_PRINTF("\r\n>>");
+		}
+	}
+}
+
+void usart_fifo_send(void)
+{
+	while(usart_fifo_out_len) {
+		uint8_t data = usart_fifo_pop();
+		usart_wait_send_ready(usart);
+		usart_send(usart, data);
+	}
+}
+static char command[128];
+static uint8_t command_len = 0;
+static uint8_t command_argindex = 0;
+
+static uint8_t usart_read_command(void)
+{
+	uint32_t fifo_len = usart_fifo_in_len;
+	while (fifo_len) {
+		uint8_t data = usart_fifo_in_pop();
+
+		if ((data >= 'A') && (data <= 'Z')) {
+			data += 'a'-'A';
+		}
+
+		if (((data >= 'a') && (data <= 'z')) || ((data >='0') && (data<='9'))) {
+			command[command_len++] = data;
+		} else if (data == ' ') {
+			if (command_len) {
+				if (command_argindex == 0) {
+					command[command_len++] = 0;
+					command_argindex = command_len;
+				} else {
+					command[command_len++] = ' ';
+				}
+			}
+		} else if (data == '\r' || data == '\n') {
+			if (command_len) {
+				command[command_len++] = 0;
+				if (!command_argindex) {
+					command_argindex = command_len;
+				}
+				return 1;
+			}
+		} else if (data == 127) {
+			if (command_len) {
+				if (command_argindex) {
+					if (command_len == command_argindex) {
+						command_argindex = 0;
+					}
+				}
+				command[command_len] = '\0';
+				command_len--;
+			}
+		} else if (data == 3) {
+			command_len = 0;
+			command_argindex = 0;
+		} else {
+			LOG_PRINTF("%d ",data);
+		}
+
+		fifo_len--;
+	}
+	return 0;
+}
+void usart_call_cmd(struct usart_commands * commands)
+{
+	uint32_t i = 0;
+	if(!usart_read_command()) {
+		return;
+	}
+	if (!command_len) {
+		LOG_PRINTF("#2");
+		return;
+	}
+	//~ for (i = 0; i < command_len; i++) {
+		//~ LOG_PRINTF("%c", command[i]);
+	//~ }
+	i=0;
+	while(commands[i].cmd != NULL) {
+		if (!strcmp((char*)command, (char*)commands[i].cmd)) {
+			if (commands[i].callback) {
+				if(command_argindex == command_len) {
+					commands[i].callback(NULL);
+				} else {
+					commands[i].callback(&command[command_argindex]);
+				}
+			}
+			usart_write("\r\n>>",4);
+			command_len = 0;
+			command_argindex = 0;
+			return;
+		} else {
+
+		}
+		i++;
+	}
+	command_len = 0;
+	command_argindex = 0;
+	LOG_PRINTF("INVALID COMMAND\r\n>>");
+}
+
+#endif
diff --git a/src/usart_helpers.h b/src/usart_helpers.h
new file mode 100644
index 0000000..2a2f561
--- /dev/null
+++ b/src/usart_helpers.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_USART_HELPERS_H
+#define USBH_USART_HELPERS_H
+
+#include "usbh_hubbed.h"
+#include <stdint.h>
+#include <stdarg.h>
+
+BEGIN_DECLS
+
+struct usart_commands{
+	const char * cmd;
+	void (*callback)(const char * arg);
+};
+
+
+void usart_init(uint32_t usart, uint32_t baudrate);
+void usart_printf(const char *str, ...);
+void usart_vprintf(const char *str, va_list va);
+void usart_fifo_send(void);
+
+void usart_call_cmd(struct usart_commands * commands);
+void usart_interrupt(void);
+
+#ifdef USART_DEBUG
+#define LOG_PRINTF(format, ...) usart_printf(format, ##__VA_ARGS__);
+#define LOG_FLUSH() usart_fifo_send()
+#else
+#define LOG_PRINTF(dummy, ...) ((void)dummy)
+#define LOG_FLUSH()
+#endif
+
+END_DECLS
+
+#endif
diff --git a/src/usbh_driver_gp_xbox.c b/src/usbh_driver_gp_xbox.c
new file mode 100644
index 0000000..52955d9
--- /dev/null
+++ b/src/usbh_driver_gp_xbox.c
@@ -0,0 +1,420 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include "usart_helpers.h"
+#include "usbh_driver_gp_xbox.h"
+#include "driver/usbh_device_driver.h"
+
+#include <stdint.h>
+#include <libopencm3/usb/usbstd.h>
+
+static void *gp_xbox_init(void *usbh_dev);
+static bool gp_xbox_analyze_descriptor(void *drvdata, void *descriptor);
+static void gp_xbox_poll(void *drvdata, uint32_t tflp);
+static void gp_xbox_remove(void *drvdata);
+
+static const usbh_dev_driver_info_t usbh_gp_xbox_driver_info = {
+	.deviceClass = 0xff,
+	.deviceSubClass = 0xff,
+	.deviceProtocol = 0xff,
+	.idVendor = 0x045e,
+	.idProduct = 0x028e,
+	.ifaceClass = 0xff,
+	.ifaceSubClass = 93,
+	.ifaceProtocol = 0x01
+};
+
+const usbh_dev_driver_t usbh_gp_xbox_driver = {
+	.init = gp_xbox_init,
+	.analyze_descriptor = gp_xbox_analyze_descriptor,
+	.poll = gp_xbox_poll,
+	.remove = gp_xbox_remove,
+	.info = &usbh_gp_xbox_driver_info
+};
+
+enum STATES {
+	STATE_INACTIVE,
+	STATE_READING_COMPLETE,
+	STATE_READING_REQUEST,
+	STATE_SET_CONFIGURATION_REQUEST,
+	STATE_SET_CONFIGURATION_EMPTY_READ,
+	STATE_SET_CONFIGURATION_COMPLETE
+};
+
+#define GP_XBOX_CORRECT_TRANSFERRED_LENGTH 20
+
+struct _gp_xbox_device {
+	usbh_device_t *usbh_device;
+	uint8_t buffer[USBH_GP_XBOX_BUFFER];
+	uint16_t endpoint_in_maxpacketsize;
+	uint8_t endpoint_in_address;
+	enum STATES state_next;
+	uint8_t endpoint_in_toggle;
+	uint8_t device_id;
+	uint8_t configuration_value;
+};
+typedef struct _gp_xbox_device gp_xbox_device_t;
+
+static gp_xbox_device_t gp_xbox_device[USBH_GP_XBOX_MAX_DEVICES];
+static const gp_xbox_config_t *gp_xbox_config;
+
+static bool initialized = false;
+static void read_gp_xbox_in(gp_xbox_device_t *gp_xbox);
+
+void gp_xbox_driver_init(const gp_xbox_config_t *config)
+{
+	if (!config) {
+		return;
+	}
+	initialized = true;
+	uint32_t i;
+	gp_xbox_config = config;
+	for (i = 0; i < USBH_GP_XBOX_MAX_DEVICES; i++) {
+		gp_xbox_device[i].state_next = STATE_INACTIVE;
+	}
+}
+
+/**
+ *
+ *
+ */
+static void *gp_xbox_init(void *usbh_dev)
+{
+	if (!initialized) {
+		LOG_PRINTF("driver not initialized");
+		return false;
+	}
+
+	uint32_t i;
+	gp_xbox_device_t *drvdata = 0;
+
+	// find free data space for gp_xbox device
+	for (i = 0; i < USBH_GP_XBOX_MAX_DEVICES; i++) {
+		if (gp_xbox_device[i].state_next == STATE_INACTIVE) {
+			drvdata = &gp_xbox_device[i];
+			drvdata->device_id = i;
+			drvdata->endpoint_in_address = 0;
+			drvdata->endpoint_in_toggle = 0;
+			drvdata->usbh_device = usbh_dev;
+			break;
+		}
+	}
+
+	return drvdata;
+}
+
+/**
+ * Returns true if all needed data are parsed
+ */
+static bool gp_xbox_analyze_descriptor(void *drvdata, void *descriptor)
+{
+	gp_xbox_device_t *gp_xbox = drvdata;
+	uint8_t desc_type = ((uint8_t *)descriptor)[1];
+	switch (desc_type) {
+	case USB_DT_CONFIGURATION:
+		{
+			struct usb_config_descriptor *cfg = (struct usb_config_descriptor*)descriptor;
+			gp_xbox->configuration_value = cfg->bConfigurationValue;
+		}
+		break;
+	case USB_DT_DEVICE:
+		break;
+	case USB_DT_INTERFACE:
+		break;
+	case USB_DT_ENDPOINT:
+		{
+			struct usb_endpoint_descriptor *ep = (struct usb_endpoint_descriptor*)descriptor;
+			if ((ep->bmAttributes&0x03) == USB_ENDPOINT_ATTR_INTERRUPT) {
+				uint8_t epaddr = ep->bEndpointAddress;
+				if (epaddr & (1<<7)) {
+					gp_xbox->endpoint_in_address = epaddr&0x7f;
+					if (ep->wMaxPacketSize < USBH_GP_XBOX_BUFFER) {
+						gp_xbox->endpoint_in_maxpacketsize = ep->wMaxPacketSize;
+					} else {
+						gp_xbox->endpoint_in_maxpacketsize = USBH_GP_XBOX_BUFFER;
+					}
+				}
+
+				if (gp_xbox->endpoint_in_address) {
+					gp_xbox->state_next = STATE_SET_CONFIGURATION_REQUEST;
+					return true;
+				}
+			}
+		}
+		break;
+	// TODO Class Specific descriptors
+	default:
+		break;
+	}
+	return false;
+}
+
+static void parse_data(usbh_device_t *dev)
+{
+	gp_xbox_device_t *gp_xbox = dev->drvdata;
+
+	uint8_t *packet = gp_xbox->buffer;
+
+	gp_xbox_packet_t gp_xbox_packet;
+	gp_xbox_packet.buttons = 0;
+
+	// DPAD
+	const uint8_t data1 = packet[2];
+	const uint8_t data2 = packet[3];
+	if (data1 & (1 << 0)) {
+		gp_xbox_packet.buttons |= GP_XBOX_DPAD_TOP;
+	}
+
+	if (data1 & (1 << 1)) {
+		gp_xbox_packet.buttons |= GP_XBOX_DPAD_BOTTOM;
+	}
+
+	if (data1 & (1 << 2)) {
+		gp_xbox_packet.buttons |= GP_XBOX_DPAD_LEFT;
+	}
+
+	if (data1 & (1 << 3)) {
+		gp_xbox_packet.buttons |= GP_XBOX_DPAD_RIGHT;
+	}
+
+	// Start + select
+
+	if (data1 & (1 << 4)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_START;
+	}
+
+	if (data1 & (1 << 5)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_SELECT;
+	}
+
+	// axis buttons
+
+	if (data1 & (1 << 6)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_AXIS_LEFT;
+	}
+
+	if (data1 & (1 << 7)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_AXIS_RIGHT;
+	}
+
+	// buttons ABXY
+
+	if (data2 & (1 << 4)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_A;
+	}
+
+	if (data2 & (1 << 5)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_B;
+	}
+
+	if (data2 & (1 << 6)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_X;
+	}
+
+	if (data2 & (1 << 7)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_Y;
+	}
+
+	// buttons rear
+
+	if (data2 & (1 << 0)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_LT;
+	}
+
+	if (data2 & (1 << 1)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_RT;
+	}
+
+	if (data2 & (1 << 2)) {
+		gp_xbox_packet.buttons |= GP_XBOX_BUTTON_XBOX;
+	}
+
+	// rear levers
+
+	gp_xbox_packet.axis_rear_left = packet[4];
+	gp_xbox_packet.axis_rear_right = packet[5];
+	gp_xbox_packet.axis_left_x = packet[7]*256 + packet[6];
+	gp_xbox_packet.axis_left_y = packet[9]*256 + packet[8];
+	gp_xbox_packet.axis_right_x = packet[11]*256 + packet[10];
+	gp_xbox_packet.axis_right_y = packet[13]*256 + packet[12];
+
+	// call update callback
+	if (gp_xbox_config->update) {
+		gp_xbox_config->update(gp_xbox->device_id, gp_xbox_packet);
+	}
+}
+
+static void event(usbh_device_t *dev, usbh_packet_callback_data_t cb_data)
+{
+	gp_xbox_device_t *gp_xbox = dev->drvdata;
+	switch (gp_xbox->state_next) {
+	case STATE_READING_COMPLETE:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				parse_data(dev);
+				gp_xbox->state_next = STATE_READING_REQUEST;
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				if (cb_data.transferred_length == GP_XBOX_CORRECT_TRANSFERRED_LENGTH) {
+					parse_data(dev);
+				}
+				gp_xbox->state_next = STATE_READING_REQUEST;
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+				ERROR(cb_data.status);
+				gp_xbox->state_next = STATE_INACTIVE;
+				break;
+			}
+		}
+		break;
+
+	case STATE_SET_CONFIGURATION_EMPTY_READ:
+		{
+			LOG_PRINTF("|empty packet read|");
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				gp_xbox->state_next = STATE_SET_CONFIGURATION_COMPLETE;
+				device_xfer_control_read(0, 0, event, dev);
+				break;
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				gp_xbox->state_next = STATE_INACTIVE;
+				break;
+			}
+		}
+		break;
+	case STATE_SET_CONFIGURATION_COMPLETE: // Configured
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				gp_xbox->state_next = STATE_READING_REQUEST;
+				gp_xbox->endpoint_in_toggle = 0;
+				LOG_PRINTF("\r\ngp_xbox CONFIGURED\r\n");
+				if (gp_xbox_config->notify_connected) {
+					gp_xbox_config->notify_connected(gp_xbox->device_id);
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				gp_xbox->state_next = STATE_INACTIVE;
+				break;
+			}
+		}
+		break;
+
+	case STATE_INACTIVE:
+		{
+			LOG_PRINTF("XBOX inactive");
+		}
+		break;
+	default:
+		{
+			LOG_PRINTF("Unknown state\r\n");
+		}
+		break;
+	}
+}
+
+
+static void read_gp_xbox_in(gp_xbox_device_t *gp_xbox)
+{
+	usbh_packet_t packet;
+
+	packet.address = gp_xbox->usbh_device->address;
+	packet.data = &gp_xbox->buffer[0];
+	packet.datalen = gp_xbox->endpoint_in_maxpacketsize;
+	packet.endpoint_address = gp_xbox->endpoint_in_address;
+	packet.endpoint_size_max = gp_xbox->endpoint_in_maxpacketsize;
+	packet.endpoint_type = USBH_EPTYP_BULK;
+	packet.speed = gp_xbox->usbh_device->speed;
+	packet.callback = event;
+	packet.callback_arg = gp_xbox->usbh_device;
+	packet.toggle = &gp_xbox->endpoint_in_toggle;
+
+	gp_xbox->state_next = STATE_READING_COMPLETE;
+	usbh_read(gp_xbox->usbh_device, &packet);
+
+	// LOG_PRINTF("@gp_xbox EP1 |  \r\n");
+}
+
+/**
+ *
+ *  tflp time from last poll [us]
+ */
+static void gp_xbox_poll(void *drvdata, uint32_t tflp)
+{
+	(void)tflp;
+	gp_xbox_device_t *gp_xbox = drvdata;
+	usbh_device_t *dev = gp_xbox->usbh_device;
+
+	switch (gp_xbox->state_next) {
+	case STATE_READING_REQUEST:
+		{
+			read_gp_xbox_in(gp_xbox);
+		}
+		break;
+
+	case STATE_SET_CONFIGURATION_REQUEST:
+		{
+			struct usb_setup_data setup_data;
+
+			setup_data.bmRequestType = 0b00000000;
+			setup_data.bRequest = USB_REQ_SET_CONFIGURATION;
+			setup_data.wValue = gp_xbox->configuration_value;
+			setup_data.wIndex = 0;
+			setup_data.wLength = 0;
+
+			gp_xbox->state_next = STATE_SET_CONFIGURATION_EMPTY_READ;
+
+			device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+		}
+		break;
+
+	default:
+		{
+			// do nothing - probably transfer is in progress
+		}
+		break;
+	}
+}
+
+static void gp_xbox_remove(void *drvdata)
+{
+	LOG_PRINTF("Removing xbox\r\n");
+
+	gp_xbox_device_t *gp_xbox = (gp_xbox_device_t *)drvdata;
+	if (gp_xbox_config->notify_disconnected) {
+		gp_xbox_config->notify_disconnected(gp_xbox->device_id);
+	}
+	gp_xbox->state_next = STATE_INACTIVE;
+	gp_xbox->endpoint_in_address = 0;
+}
diff --git a/src/usbh_driver_hid_mouse.c b/src/usbh_driver_hid_mouse.c
new file mode 100644
index 0000000..5c01451
--- /dev/null
+++ b/src/usbh_driver_hid_mouse.c
@@ -0,0 +1,294 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "usbh_hubbed.h"
+#include "driver/usbh_device_driver.h"
+#include "usbh_driver_hid_mouse.h"
+#include "usart_helpers.h"
+
+#include <libopencm3/usb/usbstd.h>
+
+static void *mouse_init(void *usbh_dev);
+static bool mouse_analyze_descriptor(void *drvdata, void *descriptor);
+static void mouse_poll(void *drvdata, uint32_t tflp);
+static void mouse_remove(void *drvdata);
+
+static const usbh_dev_driver_info_t usbh_hid_mouse_driver_info = {
+	.deviceClass = -1,
+	.deviceSubClass = -1,
+	.deviceProtocol = -1,
+	.idVendor = -1,
+	.idProduct = -1,
+	.ifaceClass = 0x03,
+	.ifaceSubClass = -1,
+	.ifaceProtocol = 0x02
+};
+
+const usbh_dev_driver_t usbh_hid_mouse_driver = {
+	.init = mouse_init,
+	.analyze_descriptor = mouse_analyze_descriptor,
+	.poll = mouse_poll,
+	.remove = mouse_remove,
+	.info = &usbh_hid_mouse_driver_info
+};
+
+enum STATES {
+	STATE_INACTIVE,
+	STATE_READING_COMPLETE,
+	STATE_READING_REQUEST,
+	STATE_SET_CONFIGURATION_REQUEST,
+	STATE_SET_CONFIGURATION_EMPTY_READ,
+	STATE_SET_CONFIGURATION_COMPLETE
+};
+
+struct _hid_mouse_device {
+	usbh_device_t *usbh_device;
+	uint8_t buffer[USBH_HID_MOUSE_BUFFER];
+	uint16_t endpoint_in_maxpacketsize;
+	uint8_t endpoint_in_address;
+	enum STATES state_next;
+	uint8_t endpoint_in_toggle;
+	uint8_t device_id;
+	uint8_t configuration_value;
+};
+typedef struct _hid_mouse_device hid_mouse_device_t;
+
+static hid_mouse_device_t mouse_device[USBH_HID_MOUSE_MAX_DEVICES];
+static const hid_mouse_config_t *mouse_config;
+
+
+
+
+#include <stdint.h>
+
+
+
+void hid_mouse_driver_init(const hid_mouse_config_t *config)
+{
+	uint32_t i;
+	mouse_config = config;
+	for (i = 0; i < USBH_HID_MOUSE_MAX_DEVICES; i++) {
+		mouse_device[i].state_next = STATE_INACTIVE;
+	}
+}
+
+/**
+ *
+ *
+ */
+static void *mouse_init(void *usbh_dev)
+{
+	uint32_t i;
+	hid_mouse_device_t *drvdata = 0;
+
+	// find free data space for mouse device
+	for (i = 0; i < USBH_HID_MOUSE_MAX_DEVICES; i++) {
+		if (mouse_device[i].state_next == STATE_INACTIVE) {
+			drvdata = &mouse_device[i];
+			drvdata->device_id = i;
+			drvdata->endpoint_in_address = 0;
+			drvdata->endpoint_in_toggle = 0;
+			drvdata->usbh_device = usbh_dev;
+			break;
+		}
+	}
+
+	return drvdata;
+}
+
+/**
+ * Returns true if all needed data are parsed
+ */
+static bool mouse_analyze_descriptor(void *drvdata, void *descriptor)
+{
+	hid_mouse_device_t *mouse = drvdata;
+	uint8_t desc_type = ((uint8_t *)descriptor)[1];
+	switch (desc_type) {
+	case USB_DT_CONFIGURATION:
+		{
+			struct usb_config_descriptor *cfg = (struct usb_config_descriptor*)descriptor;
+			mouse->configuration_value = cfg->bConfigurationValue;
+		}
+		break;
+	case USB_DT_DEVICE:
+		break;
+	case USB_DT_INTERFACE:
+		break;
+	case USB_DT_ENDPOINT:
+		{
+			struct usb_endpoint_descriptor *ep = (struct usb_endpoint_descriptor*)descriptor;
+			if ((ep->bmAttributes&0x03) == USB_ENDPOINT_ATTR_INTERRUPT) {
+				uint8_t epaddr = ep->bEndpointAddress;
+				if (epaddr & (1<<7)) {
+					mouse->endpoint_in_address = epaddr&0x7f;
+					if (ep->wMaxPacketSize < USBH_HID_MOUSE_BUFFER) {
+						mouse->endpoint_in_maxpacketsize = ep->wMaxPacketSize;
+					} else {
+						mouse->endpoint_in_maxpacketsize = USBH_HID_MOUSE_BUFFER;
+					}
+				}
+
+				if (mouse->endpoint_in_address) {
+					mouse->state_next = STATE_SET_CONFIGURATION_REQUEST;
+					return true;
+				}
+			}
+		}
+		break;
+	// TODO Class Specific descriptors
+	default:
+		break;
+	}
+	return false;
+}
+
+static void event(usbh_device_t *dev, usbh_packet_callback_data_t cb_data)
+{
+	hid_mouse_device_t *mouse = dev->drvdata;
+	switch (mouse->state_next) {
+	case STATE_READING_COMPLETE:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				mouse->state_next = STATE_READING_REQUEST;
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+				ERROR(cb_data.status);
+				mouse->state_next = STATE_INACTIVE;
+				break;
+			}
+		}
+		break;
+
+	case STATE_SET_CONFIGURATION_EMPTY_READ:
+		{
+			LOG_PRINTF("|empty packet read|");
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				mouse->state_next++;
+				device_xfer_control_read(0, 0, event, dev);
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+				ERROR(cb_data.status);
+				mouse->state_next = STATE_INACTIVE;
+				break;
+			}
+		}
+		break;
+	case STATE_SET_CONFIGURATION_COMPLETE: // Configured
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				mouse->state_next = STATE_READING_REQUEST;
+				mouse->endpoint_in_toggle = 0;
+				LOG_PRINTF("\r\nMOUSE CONFIGURED\r\n");
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+				ERROR(cb_data.status);
+				mouse->state_next = STATE_INACTIVE;
+				break;
+			}
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+
+static void read_mouse_in(void *drvdata)
+{
+	hid_mouse_device_t *mouse = drvdata;
+	usbh_packet_t packet;
+
+	packet.address = mouse->usbh_device->address;
+	packet.data = &mouse->buffer[0];
+	packet.datalen = mouse->endpoint_in_maxpacketsize;
+	packet.endpoint_address = mouse->endpoint_in_address;
+	packet.endpoint_size_max = mouse->endpoint_in_maxpacketsize;
+	packet.endpoint_type = USBH_EPTYP_BULK;
+	packet.speed = mouse->usbh_device->speed;
+	packet.callback = event;
+	packet.callback_arg = mouse->usbh_device;
+	packet.toggle = &mouse->endpoint_in_toggle;
+
+	mouse->state_next = STATE_READING_COMPLETE;
+	usbh_read(mouse->usbh_device, &packet);
+
+	// LOG_PRINTF("@MOUSE EP1 |  \r\n");
+
+}
+
+/**
+ *
+ *  tflp time from last poll [us]
+ */
+static void mouse_poll(void *drvdata, uint32_t tflp)
+{
+	(void)drvdata;
+	(void)tflp;
+	hid_mouse_device_t *mouse = drvdata;
+	usbh_device_t *dev = mouse->usbh_device;
+	switch (mouse->state_next) {
+	case STATE_READING_REQUEST:
+		{
+			read_mouse_in(drvdata);
+		}
+		break;
+
+	case STATE_SET_CONFIGURATION_REQUEST:
+		{
+			struct usb_setup_data setup_data;
+
+			setup_data.bmRequestType = 0b00000000;
+			setup_data.bRequest = USB_REQ_SET_CONFIGURATION;
+			setup_data.wValue = mouse->configuration_value;
+			setup_data.wIndex = 0;
+			setup_data.wLength = 0;
+
+			mouse->state_next++;
+
+			device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+		}
+		break;
+
+	default:
+		// do nothing - probably transfer is in progress
+		break;
+	}
+}
+
+static void mouse_remove(void *drvdata)
+{
+	hid_mouse_device_t *mouse = (hid_mouse_device_t *)drvdata;
+	mouse->state_next = STATE_INACTIVE;
+	mouse->endpoint_in_address = 0;
+}
diff --git a/src/usbh_driver_hub.c b/src/usbh_driver_hub.c
new file mode 100644
index 0000000..c82eacd
--- /dev/null
+++ b/src/usbh_driver_hub.c
@@ -0,0 +1,865 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "usbh_driver_hub_private.h"
+#include "driver/usbh_device_driver.h"
+#include "usart_helpers.h"
+#include "usbh_config.h"
+
+#include <stdint.h>
+
+
+static hub_device_t hub_device[USBH_MAX_HUBS];
+
+static void *hub_init(void *usbh_dev);
+static bool hub_analyze_descriptor(void *drvdata, void *descriptor);
+static void hub_poll(void *drvdata, uint32_t tflp);
+static void event(usbh_device_t *dev, usbh_packet_callback_data_t cb_data);
+static void hub_remove(void *drvdata);
+
+static const usbh_dev_driver_info_t usbh_hub_driver_info = {
+	.deviceClass = 0x09,
+	.deviceSubClass = -1,
+	.deviceProtocol = -1,
+	.idVendor = -1,
+	.idProduct = -1,
+	.ifaceClass = 0x09,
+	.ifaceSubClass = -1,
+	.ifaceProtocol = -1
+};
+
+const usbh_dev_driver_t usbh_hub_driver = {
+	.init = hub_init,
+	.analyze_descriptor = hub_analyze_descriptor,
+	.poll = hub_poll,
+	.remove = hub_remove,
+	.info = &usbh_hub_driver_info
+};
+
+
+
+void hub_driver_init(void)
+{
+	uint32_t i;
+
+	for (i = 0; i < USBH_MAX_HUBS; i++) {
+		hub_device[i].device[0] = 0;
+		hub_device[i].ports_num = 0;
+		hub_device[i].current_port = -1;
+	}
+}
+
+
+/**
+ *
+ *
+ */
+static void *hub_init(void *usbh_dev)
+{
+	uint32_t i;
+	hub_device_t *drvdata = 0;
+	// find free data space for hub device
+	for (i = 0; i < USBH_MAX_HUBS; i++) {
+		if (hub_device[i].device[0] == 0) {
+			break;
+		}
+	}
+	LOG_PRINTF("%{%d}",i);
+    LOG_FLUSH();
+	if (i == USBH_MAX_HUBS) {
+		LOG_PRINTF("ERRRRRRR");
+		return 0;
+	}
+
+	drvdata = &hub_device[i];
+	drvdata->state = 0;
+	drvdata->ports_num = 0;
+	drvdata->device[0] = usbh_dev;
+	drvdata->busy = 0;
+	drvdata->endpoint_in_address = 0;
+	drvdata->endpoint_in_maxpacketsize = 0;
+
+//	for (i = 1; i < USBH_MAX_HUBS + 1; i++) {
+//		drvdata->device[i]->address = 0;
+//		drvdata->device[i]->state = 0;
+//	}
+	return drvdata;
+}
+
+/**
+ * Returns true if all needed data are parsed
+ */
+static bool hub_analyze_descriptor(void *drvdata, void *descriptor)
+{
+	hub_device_t *hub = drvdata;
+	uint8_t desc_type = ((uint8_t *)descriptor)[1];
+	switch (desc_type) {
+	case USB_DT_CONFIGURATION:
+		{
+			struct usb_config_descriptor *cfg = (struct usb_config_descriptor*)descriptor;
+			hub->buffer[0] = cfg->bConfigurationValue;
+		}
+		break;
+
+	case USB_DT_ENDPOINT:
+		{
+			struct usb_endpoint_descriptor *ep = (struct usb_endpoint_descriptor *)descriptor;
+			if ((ep->bmAttributes&0x03) == USB_ENDPOINT_ATTR_INTERRUPT) {
+				uint8_t epaddr = ep->bEndpointAddress;
+				if (epaddr & (1<<7)) {
+					hub->endpoint_in_address = epaddr&0x7f;
+					hub->endpoint_in_maxpacketsize = ep->wMaxPacketSize;
+				}
+			}
+			LOG_PRINTF("ENDPOINT DESCRIPTOR FOUND\r\n");
+		}
+		break;
+
+	case USB_DT_HUB:
+		{
+			struct usb_hub_descriptor *desc = (struct usb_hub_descriptor *)descriptor;
+			//~ hub->ports_num = desc->head.bNbrPorts;
+			if ( desc->head.bNbrPorts <= USBH_HUB_MAX_DEVICES) {
+				hub->ports_num = desc->head.bNbrPorts;
+			} else {
+				LOG_PRINTF("INCREASE NUMBER OF ENABLED PORTS\r\n");
+				hub->ports_num = USBH_HUB_MAX_DEVICES;
+			}
+			LOG_PRINTF("HUB DESCRIPTOR FOUND \r\n");
+		}
+		break;
+
+	default:
+		LOG_PRINTF("TYPE: %02X \r\n",desc_type);
+		break;
+	}
+
+	if (hub->endpoint_in_address) {
+		hub->state = 1;
+		LOG_PRINTF("end enum");
+		return true;
+	}
+	return false;
+}
+
+// Enumerate
+static void event(usbh_device_t *dev, usbh_packet_callback_data_t cb_data)
+{
+	//~ usbh_device_t *dev = arg;
+	hub_device_t *hub = dev->drvdata;
+
+	LOG_PRINTF("\r\nHUB->STATE = %d\r\n", hub->state);
+	switch (hub->state) {
+	case 26:
+		switch (cb_data.status) {
+		case USBH_PACKET_CALLBACK_STATUS_OK:
+			{
+				uint8_t i;
+				uint8_t *buf = hub->buffer;
+				uint32_t psc = 0;	// Limit: up to 4 bytes...
+				for (i = 0; i < cb_data.transferred_length; i++) {
+					psc += buf[i] << (i*8);
+				}
+				int8_t port = 0;
+
+				LOG_PRINTF("psc:%d\r\n",psc);
+				// Driver error... port not found
+				if (!psc) {
+					// Continue reading status change endpoint
+					hub->state = 25;
+					break;
+				}
+
+				for (i = 0; i <= hub->ports_num; i++) {
+					if (psc & (1<<i)) {
+						port = i;
+						psc = 0;
+						break;
+					}
+				}
+
+				if (hub->current_port >= 1) {
+					if (hub->current_port != port) {
+						LOG_PRINTF("X");
+						hub->state = 25;
+						break;
+					}
+				}
+				struct usb_setup_data setup_data;
+				// If regular port event, else hub event
+				if (port) {
+					setup_data.bmRequestType = 0b10100011;
+				} else {
+					setup_data.bmRequestType = 0b10100000;
+				}
+
+
+				//~ LOG_PRINTF("port:%d", port);
+				setup_data.bRequest = USB_REQ_GET_STATUS;
+				setup_data.wValue = 0;
+				setup_data.wIndex = port;
+				setup_data.wLength = 4;
+				hub->state = 31;
+
+				hub->current_port = port;
+				LOG_PRINTF("\r\n\r\nPORT FOUND: %d\r\n", port);
+				device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+			}
+			break;
+
+		case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+		case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+			ERROR(cb_data.status);
+			hub->state = 0;
+			break;
+
+		case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+
+			// In case of EAGAIN error, retry read on status endpoint
+			hub->state = 25;
+			LOG_PRINTF("HUB: Retrying...\r\n");
+			break;
+		}
+		break;
+
+	case 2:
+		{
+			LOG_PRINTF("|empty packet read|");
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				device_xfer_control_read(0, 0, event, dev);
+				hub->state++;
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+
+	case 3: // Get HUB Descriptor write
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				if (hub->ports_num) {
+					hub->index = 0;
+					hub->state = 6;
+					LOG_PRINTF("No need to get HUB DESC\r\n");
+					event(dev, cb_data);
+				} else {
+					hub->endpoint_in_toggle = 0;
+
+					struct usb_setup_data setup_data;
+					hub->desc_len = hub->device[0]->packet_size_max0;
+
+					setup_data.bmRequestType = 0b10100000;
+					setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+					setup_data.wValue = 0x29<<8;
+					setup_data.wIndex = 0;
+					setup_data.wLength = hub->desc_len;
+
+					hub->state++;
+					device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+					LOG_PRINTF("DO Need to get HUB DESC\r\n");
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+
+	case 4: // Get HUB Descriptor read
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				hub->state++;
+				device_xfer_control_read(hub->buffer, hub->desc_len, event, dev); // "error dynamic size" - bad comment, investigate
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+
+	case 5:// Hub descriptor found
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					struct usb_hub_descriptor *hub_descriptor =
+						(struct usb_hub_descriptor *)hub->buffer;
+
+					// Check size
+					if (hub_descriptor->head.bDescLength > hub->desc_len) {
+						struct usb_setup_data setup_data;
+						hub->desc_len = hub_descriptor->head.bDescLength;
+
+						setup_data.bmRequestType = 0b10100000;
+						setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+						setup_data.wValue = 0x29<<8;
+						setup_data.wIndex = 0;
+						setup_data.wLength = hub->desc_len;
+
+						hub->state = 4;
+						device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+						break;
+					} else if (hub_descriptor->head.bDescLength == hub->desc_len) {
+						hub->ports_num = hub_descriptor->head.bNbrPorts;
+
+						hub->state++;
+						hub->index = 0;
+						cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+						event(dev, cb_data);
+					} else {
+						//try again
+					}
+				}
+				break;
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				{
+					LOG_PRINTF("->\t\t\t\t\t ERRSIZ: deschub\r\n");
+					struct usb_hub_descriptor*hub_descriptor =
+						(struct usb_hub_descriptor *)hub->buffer;
+
+					if (cb_data.transferred_length >= sizeof(struct usb_hub_descriptor_head)) {
+						if (cb_data.transferred_length == hub_descriptor->head.bDescLength) {
+							// Process HUB descriptor
+							if ( hub_descriptor->head.bNbrPorts <= USBH_HUB_MAX_DEVICES) {
+								hub->ports_num = hub_descriptor->head.bNbrPorts;
+							} else {
+								LOG_PRINTF("INCREASE NUMBER OF ENABLED PORTS\r\n");
+								hub->ports_num = USBH_HUB_MAX_DEVICES;
+							}
+							hub->state++;
+							hub->index = 0;
+
+							cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+							event(dev, cb_data);
+						}
+					}
+				}
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+
+	case 6:// enable ports
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				if (hub->index < hub->ports_num) {
+					hub->index++;
+					struct usb_setup_data setup_data;
+
+					LOG_PRINTF("[!%d!]",hub->index);
+					setup_data.bmRequestType = 0b00100011;
+					setup_data.bRequest = HUB_REQ_SET_FEATURE;
+					setup_data.wValue = HUB_FEATURE_PORT_POWER;
+					setup_data.wIndex = hub->index;
+					setup_data.wLength = 0;
+
+					device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+				} else {
+					hub->state++;
+					// TODO:
+					// Delay Based on hub descriptor field bPwr2PwrGood
+					// delay_ms_busy_loop(200);
+
+					LOG_PRINTF("\r\nHUB CONFIGURED & PORTS POWERED\r\n");
+
+					cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+					event(dev, cb_data);
+				}
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+
+	case 7:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					struct usb_setup_data setup_data;
+
+					setup_data.bmRequestType = 0b10100000;
+					setup_data.bRequest = USB_REQ_GET_STATUS;
+					setup_data.wValue = 0;
+					setup_data.wIndex = 0;
+					setup_data.wLength = 4;
+
+					hub->state++;
+					device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				break;
+			}
+
+		}
+		break;
+	case 8:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				device_xfer_control_read(hub->buffer, 4, event, dev);
+				hub->index = 0;
+				hub->state++;
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+	case 9:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					struct usb_setup_data setup_data;
+
+					setup_data.bmRequestType = 0b10100011;
+					setup_data.bRequest = USB_REQ_GET_STATUS;
+					setup_data.wValue = 0;
+					setup_data.wIndex = ++hub->index;
+					setup_data.wLength = 4;
+
+					hub->state++;
+
+					device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+	case 10:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				device_xfer_control_read(hub->buffer, 4, event, dev);
+				hub->state++;
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+	case 11:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				if (hub->index < hub->ports_num) {
+					hub->state = 9;
+					// process data contained in hub->buffer
+					// TODO:
+					cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+					event(dev, cb_data);
+				} else {
+					hub->busy = 0;
+					hub->state = 25;
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+
+	case 31: // Read port status
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					int8_t port = hub->current_port;
+					hub->state++;
+
+					// TODO: rework to endianess aware,
+					// (maybe whole library is affected by this)
+					// Detail:
+					// 	Isn't universal. Here is endianess ok,
+					// 	but on another architecture must not be
+					device_xfer_control_read(&hub->hub_and_port_status[port], 4, event, dev);
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				// continue
+				hub->state = 25;
+				break;
+			}
+
+		}
+		break;
+	case 32:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					int8_t port = hub->current_port;
+					LOG_PRINTF("|%d",port);
+
+
+					// Get Port status, else Get Hub status
+					if (port) {
+						uint16_t stc = hub->hub_and_port_status[port].stc;
+
+						// Connection status changed
+						if (stc & (1<<HUB_FEATURE_PORT_CONNECTION)) {
+
+							// Check, whether device is in connected state
+							if (!hub->device[port]) {
+								if (!usbh_enum_available() || hub->busy) {
+									LOG_PRINTF("\r\n\t\t\tCannot enumerate %d %d\r\n", !usbh_enum_available(), hub->busy);
+									hub->state = 25;
+									break;
+								}
+							}
+
+							// clear feature C_PORT_CONNECTION
+							struct usb_setup_data setup_data;
+
+							setup_data.bmRequestType = 0b00100011;
+							setup_data.bRequest = HUB_REQ_CLEAR_FEATURE;
+							setup_data.wValue = HUB_FEATURE_C_PORT_CONNECTION;
+							setup_data.wIndex = port;
+							setup_data.wLength = 0;
+
+							hub->state = 33;
+							device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+
+						} else if(stc & (1<<HUB_FEATURE_PORT_RESET)) {
+							// clear feature C_PORT_RESET
+							// Reset processing is complete, enumerate device
+							struct usb_setup_data setup_data;
+
+							setup_data.bmRequestType = 0b00100011;
+							setup_data.bRequest = HUB_REQ_CLEAR_FEATURE;
+							setup_data.wValue = HUB_FEATURE_C_PORT_RESET;
+							setup_data.wIndex = port;
+							setup_data.wLength = 0;
+
+							hub->state = 35;
+
+							LOG_PRINTF("RESET");
+							device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+						} else {
+							LOG_PRINTF("another STC %d\r\n", stc);
+						}
+					} else {
+						hub->state = 25;
+						LOG_PRINTF("HUB status change\r\n");
+					}
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				// continue
+				hub->state = 25;
+				break;
+			}
+		}
+		break;
+	case 33:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					int8_t port = hub->current_port;
+					uint16_t stc = hub->hub_and_port_status[port].stc;
+					if (!hub->device[port]) {
+						if ((stc) & (1<<HUB_FEATURE_PORT_CONNECTION)) {
+							struct usb_setup_data setup_data;
+
+							setup_data.bmRequestType = 0b00100011;
+							setup_data.bRequest = HUB_REQ_SET_FEATURE;
+							setup_data.wValue = HUB_FEATURE_PORT_RESET;
+							setup_data.wIndex = port;
+							setup_data.wLength = 0;
+
+							hub->state = 11;
+							LOG_PRINTF("CONN");
+
+							hub->busy = 1;
+							device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+						}
+					} else {
+						LOG_PRINTF("\t\t\t\tDISCONNECT EVENT\r\n");
+						if (hub->device[port]->drv && hub->device[port]->drvdata) {
+							hub->device[port]->drv->remove(hub->device[port]->drvdata);
+						}
+						hub->device[port]->address = -1;
+
+						hub->device[port]->drv = 0;
+						hub->device[port]->drvdata = 0;
+						hub->device[port] = 0;
+						hub->current_port = CURRENT_PORT_NONE;
+						hub->state = 25;
+						hub->busy = 0;
+					}
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				// continue
+				hub->state = 25;
+				break;
+			}
+		}
+		break;
+	case 35:	// RESET COMPLETE, start enumeration
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					LOG_PRINTF("\r\nPOLL\r\n");
+					int8_t port = hub->current_port;
+					uint16_t sts = hub->hub_and_port_status[port].sts;
+
+
+					if (sts & (1<<HUB_FEATURE_PORT_ENABLE)) {
+						hub->device[port] = usbh_get_free_device(dev);
+
+						if (!hub->device[port]) {
+							LOG_PRINTF("\r\nFATAL ERROR\r\n");
+							return;// DEAD END
+						}
+						if ((sts & (1<<(HUB_FEATURE_PORT_LOWSPEED))) &&
+							!(sts & (1<<(HUB_FEATURE_PORT_HIGHSPEED)))) {
+							LOG_PRINTF("Low speed device");
+
+							// Disable Low speed device immediately
+							struct usb_setup_data setup_data;
+
+							setup_data.bmRequestType = 0b00100011;
+							setup_data.bRequest = HUB_REQ_CLEAR_FEATURE;
+							setup_data.wValue = HUB_FEATURE_PORT_ENABLE;
+							setup_data.wIndex = port;
+							setup_data.wLength = 0;
+
+							// After write process another devices, poll for events
+							hub->state = 11;//Expecting all ports are powered (constant/non-changeable after init)
+							hub->current_port = CURRENT_PORT_NONE;
+							device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+						} else if (!(sts & (1<<(HUB_FEATURE_PORT_LOWSPEED))) &&
+							!(sts & (1<<(HUB_FEATURE_PORT_HIGHSPEED)))) {
+							hub->device[port]->speed = USBH_SPEED_FULL;
+							LOG_PRINTF("Full speed device");
+							hub->timestamp_us = hub->time_curr_us;
+							hub->state = 100; // schedule wait for 500ms
+						}
+
+
+					} else {
+						LOG_PRINTF("%s:%d Do not know what to do, when device is disabled after reset\r\n", __FILE__, __LINE__);
+						hub->state = 25;
+						return;
+					}
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				ERROR(cb_data.status);
+				// continue
+				hub->state = 25;
+				break;
+			}
+		}
+		break;
+	default:
+		LOG_PRINTF("UNHANDLED EVENT %d\r\n",hub->state);
+		break;
+	}
+}
+
+static void read_ep1(void *drvdata)
+{
+	hub_device_t *hub = drvdata;
+	usbh_packet_t packet;
+
+	packet.address = hub->device[0]->address;
+	packet.data = hub->buffer;
+	packet.datalen = hub->endpoint_in_maxpacketsize;
+	packet.endpoint_address = hub->endpoint_in_address;
+	packet.endpoint_size_max = hub->endpoint_in_maxpacketsize;
+	packet.endpoint_type = USBH_EPTYP_INTERRUPT;
+	packet.speed = hub->device[0]->speed;
+	packet.callback = event;
+	packet.callback_arg = hub->device[0];
+	packet.toggle = &hub->endpoint_in_toggle;
+
+	hub->state = 26;
+	usbh_read(hub->device[0], &packet);
+	LOG_PRINTF("@hub %d/EP1 |  \r\n", hub->device[0]->address);
+
+}
+
+/**
+ *
+ *  tflp time from last poll [ms]
+ */
+static void hub_poll(void *drvdata, uint32_t time_curr_us)
+{
+	hub_device_t *hub = drvdata;
+	usbh_device_t *dev = hub->device[0];
+
+	hub->time_curr_us = time_curr_us;
+
+	switch (hub->state) {
+	case 25:
+		{
+			if (usbh_enum_available()) {
+				read_ep1(hub);
+			} else {
+				LOG_PRINTF("enum not available\r\n");
+			}
+		}
+		break;
+
+	case 1:
+		{
+			LOG_PRINTF("CFGVAL: %d\r\n", hub->buffer[0]);
+			struct usb_setup_data setup_data;
+
+			setup_data.bmRequestType = 0b00000000;
+			setup_data.bRequest = USB_REQ_SET_CONFIGURATION;
+			setup_data.wValue = hub->buffer[0];
+			setup_data.wIndex = 0;
+			setup_data.wLength = 0;
+
+			hub->state += 2;
+			device_xfer_control_write(&setup_data, sizeof(setup_data), event, dev);
+
+		}
+		break;
+	case 100:
+		if (hub->time_curr_us - hub->timestamp_us > 500000) {
+			int8_t port = hub->current_port;
+			LOG_PRINTF("PORT: %d", port);
+			LOG_PRINTF("\r\nNEW device at address: %d\r\n", hub->device[port]->address);
+			hub->device[port]->lld = hub->device[0]->lld;
+
+			device_enumeration_start(hub->device[port]);
+			hub->current_port = CURRENT_PORT_NONE;
+
+			// Maybe error, when assigning address is taking too long
+			//
+			// Detail:
+			// USB hub cannot enable another port while the device
+			// the current one is also in address state (has address==0)
+			// Only one device on bus can have address==0
+			hub->busy = 0;
+
+			hub->state = 25;
+		}
+		break;
+	default:
+		break;
+	}
+
+	if (usbh_enum_available()) {
+		uint32_t i;
+		for (i = 1; i < USBH_HUB_MAX_DEVICES + 1; i++) {
+			if (hub->device[i]) {
+				if (hub->device[i]->drv && hub->device[i]->drvdata) {
+					hub->device[i]->drv->poll(hub->device[i]->drvdata, time_curr_us);
+				}
+			}
+		}
+	}
+}
+static void hub_remove(void *drvdata)
+{
+	hub_device_t *hub = drvdata;
+	uint8_t i;
+
+	// Call fast... to avoid polling
+	hub->state = 0;
+	hub->endpoint_in_address = 0;
+	hub->busy = 0;
+	for (i = 1; i < USBH_HUB_MAX_DEVICES + 1; i++) {
+		if (hub->device[i]) {
+			if (hub->device[i]->drv && hub->device[i]->drvdata) {
+				if (hub->device[i]->drv->remove != hub_remove) {
+					LOG_PRINTF("\t\t\t\tHUB REMOVE %d\r\n",hub->device[i]->address);
+					hub->device[i]->drv->remove(hub->device[i]->drvdata);
+				}
+			}
+			hub->device[i] = 0;
+		}
+		hub->device[0]->drv = 0;
+		hub->device[0]->drvdata = 0;
+		hub->device[0] = 0;
+
+	}
+}
diff --git a/src/usbh_driver_hub_private.h b/src/usbh_driver_hub_private.h
new file mode 100644
index 0000000..4b98880
--- /dev/null
+++ b/src/usbh_driver_hub_private.h
@@ -0,0 +1,108 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_DRIVER_HUB_PRIVATE_
+#define USBH_DRIVER_HUB_PRIVATE_
+
+#include "usbh_config.h"
+#include "driver/usbh_device_driver.h"
+#include "usbh_driver_hub.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <libopencm3/usb/usbstd.h>
+
+
+// # HUB DEFINITIONS
+#define HUB_FEATURE_PORT_CONNECTION 0
+#define HUB_FEATURE_PORT_ENABLE 1
+#define HUB_FEATURE_PORT_SUSPEND 2
+#define HUB_FEATURE_PORT_OVERCURRENT 3
+#define HUB_FEATURE_PORT_RESET 4
+#define HUB_FEATURE_PORT_POWER 8
+#define HUB_FEATURE_PORT_LOWSPEED 9
+#define HUB_FEATURE_PORT_HIGHSPEED 10
+
+#define HUB_FEATURE_C_PORT_CONNECTION 16
+#define HUB_FEATURE_C_PORT_ENABLE 17
+#define HUB_FEATURE_C_PORT_SUSPEND 18
+#define HUB_FEATURE_C_PORT_OVERCURRENT 19
+#define HUB_FEATURE_C_PORT_RESET 20
+
+#define HUB_REQ_GET_STATUS 		0
+#define HUB_REQ_CLEAR_FEATURE 	1
+#define HUB_REQ_SET_FEATURE 	3
+#define HUB_REQ_GET_DESCRIPTOR 	6
+
+#define USB_DT_HUB 		(41)
+#define USB_DT_HUB_SIZE	(9)
+// Hub buffer: must be larger than hub descriptor
+#define USBH_HUB_BUFFER_SIZE	(USB_DT_HUB_SIZE)
+
+
+#define CURRENT_PORT_NONE -1
+
+struct _hub_device {
+	usbh_device_t *device[USBH_HUB_MAX_DEVICES + 1];
+	uint8_t buffer[USBH_HUB_BUFFER_SIZE];
+	uint16_t endpoint_in_maxpacketsize;
+	uint8_t endpoint_in_address;
+	uint8_t endpoint_in_toggle;
+	uint8_t state;
+	uint8_t desc_len;
+	uint16_t ports_num;
+	int8_t index;
+	int8_t current_port;
+
+	struct {
+		uint16_t sts;
+		uint16_t stc;
+	} hub_and_port_status[USBH_HUB_MAX_DEVICES + 1];
+
+	bool busy;
+
+	uint32_t time_curr_us;
+	uint32_t timestamp_us;
+};
+
+typedef struct _hub_device hub_device_t;
+
+struct usb_hub_descriptor_head {
+	uint8_t bDescLength;
+	uint8_t bDescriptorType;
+	uint8_t bNbrPorts;
+	uint16_t wHubCharacteristics;
+	uint8_t bPwrOn2PwrGood;
+	uint8_t bHubContrCurrent;
+} __attribute__((packed));
+struct usb_hub_descriptor_body {
+	uint8_t bDeviceRemovable;
+	uint8_t PortPwrCtrlMask;
+} __attribute__((packed));
+
+// for hubs with up to 7 ports on hub
+struct usb_hub_descriptor {
+	struct usb_hub_descriptor_head head;
+	struct usb_hub_descriptor_body body[1];
+} __attribute__((packed));
+
+#endif
diff --git a/src/usbh_hubbed.c b/src/usbh_hubbed.c
new file mode 100644
index 0000000..e935f99
--- /dev/null
+++ b/src/usbh_hubbed.c
@@ -0,0 +1,634 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "usbh_config.h"
+#include "usbh_lld_stm32f4.h"
+#include "driver/usbh_device_driver.h"
+#include "usart_helpers.h"
+
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/usb/usbstd.h>
+
+static struct {
+	bool enumeration_run;
+	const usbh_driver_t * const *lld_drivers;
+	const usbh_dev_driver_t * const *dev_drivers;
+	int8_t address_temporary;
+} usbh_data = {0};
+
+static void set_enumeration(void)
+{
+	usbh_data.enumeration_run = true;
+}
+
+static void reset_enumeration(void)
+{
+	usbh_data.enumeration_run = false;
+}
+
+static bool enumeration(void)
+{
+	return usbh_data.enumeration_run;
+}
+
+/**
+ *
+ */
+static const usbh_dev_driver_t *find_driver(const usbh_dev_driver_info_t * device_info)
+{
+
+#define CHECK_PARTIAL_COMPATIBILITY(what) \
+	if (usbh_data.dev_drivers[i]->info->what != -1\
+	&& device_info->what != usbh_data.dev_drivers[i]->info->what) {\
+		i++;\
+		continue;\
+	}
+
+
+	int i = 0;
+
+	while (usbh_data.dev_drivers[i]) {
+
+		CHECK_PARTIAL_COMPATIBILITY(ifaceClass);
+		CHECK_PARTIAL_COMPATIBILITY(ifaceSubClass);
+		CHECK_PARTIAL_COMPATIBILITY(ifaceProtocol);
+		CHECK_PARTIAL_COMPATIBILITY(deviceClass);
+		CHECK_PARTIAL_COMPATIBILITY(deviceSubClass);
+		CHECK_PARTIAL_COMPATIBILITY(deviceProtocol);
+		CHECK_PARTIAL_COMPATIBILITY(idVendor);
+		CHECK_PARTIAL_COMPATIBILITY(idProduct);
+
+		return usbh_data.dev_drivers[i];
+	}
+	return 0;
+#undef CHECK_PARTIAL_COMPATIBILITY
+}
+
+
+static void device_register(void *descriptors, uint16_t descriptors_len, usbh_device_t *dev)
+{
+	uint32_t i = 0;
+	dev->drv = 0;
+	uint8_t *buf = (uint8_t *)descriptors;
+
+	dev->drv = 0;
+	dev->drvdata = 0;
+
+	uint8_t desc_len = buf[i];
+	uint8_t desc_type = buf[i + 1];
+
+	usbh_dev_driver_info_t device_info;
+	if (desc_type == USB_DT_DEVICE) {
+		struct usb_device_descriptor *device_desc = (void*)&buf[i];
+		LOG_PRINTF("DEVICE DESCRIPTOR");
+		device_info.deviceClass = device_desc->bDeviceClass;
+		device_info.deviceSubClass = device_desc->bDeviceSubClass;
+		device_info.deviceProtocol = device_desc->bDeviceProtocol;
+		device_info.idVendor = device_desc->idVendor;
+		device_info.idProduct = device_desc->idProduct;
+	} else {
+		LOG_PRINTF("INVALID descriptors pointer - fatal error");
+		return;
+	}
+
+
+	while (i < descriptors_len) {
+		desc_len = buf[i];
+		desc_type = buf[i + 1];
+		switch (desc_type) {
+		case USB_DT_DEVICE:
+		{
+			struct usb_device_descriptor *device_desc = (void*)&buf[i];
+			LOG_PRINTF("DEVICE DESCRIPTOR");
+			device_info.deviceClass = device_desc->bDeviceClass;
+			device_info.deviceSubClass = device_desc->bDeviceSubClass;
+		}
+			break;
+
+		case USB_DT_INTERFACE:
+		{
+			LOG_PRINTF("INTERFACE_DESCRIPTOR\r\n");
+			struct usb_interface_descriptor *iface = (void*)&buf[i];
+			device_info.ifaceClass = iface->bInterfaceClass;
+			device_info.ifaceSubClass = iface->bInterfaceSubClass;
+			device_info.ifaceProtocol = iface->bInterfaceProtocol;
+			const usbh_dev_driver_t *driver = find_driver(&device_info);
+			if (driver) {
+				dev->drv = driver;
+				dev->drvdata = dev->drv->init(dev);
+				if (!dev->drvdata) {
+					LOG_PRINTF("CANT TOUCH THIS");
+				}
+				break;
+			}
+		}
+			break;
+		default:
+			break;
+		}
+
+		if (desc_len == 0) {
+			LOG_PRINTF("PROBLEM WITH PARSE %d\r\n",i);
+			return;
+		}
+		i += desc_len;
+
+	}
+
+	if (dev->drv && dev->drvdata) {
+		// analyze descriptors
+		LOG_PRINTF("ANALYZE");
+		i = 0;
+		while (i < descriptors_len) {
+			desc_len = buf[i];
+			void *drvdata = dev->drvdata;
+			LOG_PRINTF("[%d]",buf[i+1]);
+			if (dev->drv->analyze_descriptor(drvdata, &buf[i])) {
+				LOG_PRINTF("Device Initialized\r\n");
+				return;
+			}
+			i += desc_len;
+		}
+	}
+	LOG_PRINTF("Device NOT Initialized\r\n");
+}
+
+void usbh_init(const void *drivers_lld[], const usbh_dev_driver_t * const device_drivers[])
+{
+	if (!drivers_lld) {
+		return;
+	}
+
+	usbh_data.lld_drivers = (const usbh_driver_t **)drivers_lld;
+	usbh_data.dev_drivers = device_drivers;
+
+	// TODO: init structures
+	uint32_t k = 0;
+	while (usbh_data.lld_drivers[k]) {
+		LOG_PRINTF("DRIVER %d\r\n", k);
+
+		usbh_device_t * usbh_device =
+			((usbh_generic_data_t *)(usbh_data.lld_drivers[k])->driver_data)->usbh_device;
+		uint32_t i;
+		for (i = 0; i < USBH_MAX_DEVICES; i++) {
+			//~ LOG_PRINTF("%p ", &usbh_device[i]);
+			usbh_device[i].address = -1;
+			usbh_device[i].drv = 0;
+			usbh_device[i].drvdata = 0;
+		}
+		LOG_PRINTF("DRIVER %d", k);
+		usbh_data.lld_drivers[k]->init(usbh_data.lld_drivers[k]->driver_data);
+
+		k++;
+	}
+
+}
+
+/*
+ * NEW ENUMERATE
+ *
+ */
+void device_xfer_control_write(void *data, uint16_t datalen, usbh_packet_callback_t callback, usbh_device_t *dev)
+{
+	usbh_packet_t packet;
+
+	packet.data = data;
+	packet.datalen = datalen;
+	packet.address = dev->address;
+	packet.endpoint_address = 0;
+	packet.endpoint_size_max = dev->packet_size_max0;
+	packet.endpoint_type = USBH_EPTYP_CONTROL;
+	packet.speed = dev->speed;
+	packet.callback = callback;
+	packet.callback_arg = dev;
+	packet.toggle = &dev->toggle0;
+
+	usbh_write(dev, &packet);
+	LOG_PRINTF("WR@device...%d |  \r\n", dev->address);
+}
+
+void device_xfer_control_read(void *data, uint16_t datalen, usbh_packet_callback_t callback, usbh_device_t *dev)
+{
+	usbh_packet_t packet;
+
+	packet.data = data;
+	packet.datalen = datalen;
+	packet.address = dev->address;
+	packet.endpoint_address = 0;
+	packet.endpoint_size_max = dev->packet_size_max0;
+	packet.endpoint_type = USBH_EPTYP_CONTROL;
+	packet.speed = dev->speed;
+	packet.callback = callback;
+	packet.callback_arg = dev;
+	packet.toggle = &dev->toggle0;
+
+	usbh_read(dev, &packet);
+	LOG_PRINTF("RD@device...%d |  \r\n", dev->address);
+}
+
+
+
+bool usbh_enum_available(void)
+{
+	return !enumeration();
+}
+
+/**
+ * Returns 0 on error
+ * device otherwise
+ */
+usbh_device_t *usbh_get_free_device(const usbh_device_t *dev)
+{
+	const usbh_driver_t *lld = dev->lld;
+	usbh_generic_data_t *lld_data = lld->driver_data;
+	usbh_device_t *usbh_device = lld_data->usbh_device;
+
+	uint8_t i;
+	LOG_PRINTF("DEV ADDRESS%d\r\n", dev->address);
+	for (i = 0; i < USBH_MAX_DEVICES; i++) {
+		if (usbh_device[i].address < 0) {
+			LOG_PRINTF("\t\t\t\t\tFOUND: %d", i);
+			usbh_device[i].address = i+1;
+			return &usbh_device[i];
+		} else {
+			LOG_PRINTF("address: %d\r\n\r\n\r\n", usbh_device[i].address);
+		}
+	}
+
+	return 0;
+}
+
+static void device_enumeration_terminate(usbh_device_t *dev)
+{
+	reset_enumeration();
+	dev->state = 0;
+	dev->address = -1;
+}
+
+/* Do not call this function directly,
+ *     only via callback passing into low-level function
+ * If you must, call it carefully ;)
+ */
+static void device_enumerate(usbh_device_t *dev, usbh_packet_callback_data_t cb_data)
+{
+	const usbh_driver_t *lld = dev->lld;
+	usbh_generic_data_t *lld_data = lld->driver_data;
+	uint8_t *usbh_buffer = lld_data->usbh_buffer;
+	uint8_t state_start = dev->state; // Detection of hang
+//	LOG_PRINTF("\r\nSTATE: %d\r\n", state);
+	switch (dev->state) {
+	case 1:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				dev->state++;
+				LOG_PRINTF("::%d::", dev->address);
+				device_xfer_control_read(0, 0, device_enumerate, dev);
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				device_enumeration_terminate(dev);
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+	case 2:
+		switch (cb_data.status) {
+		case USBH_PACKET_CALLBACK_STATUS_OK:
+			if (dev->address == 0) {
+				dev->address = usbh_data.address_temporary;
+				LOG_PRINTF("ADDR: %d\r\n", dev->address);
+			}
+
+			struct usb_setup_data setup_data;
+
+			setup_data.bmRequestType = 0b10000000;
+			setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+			setup_data.wValue = USB_DT_DEVICE << 8;
+			setup_data.wIndex = 0;
+			setup_data.wLength = USB_DT_DEVICE_SIZE;
+
+			dev->state++;
+			device_xfer_control_write(&setup_data, sizeof(setup_data),
+				device_enumerate, dev);
+			break;
+
+		case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+		case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+		case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+			device_enumeration_terminate(dev);
+			ERROR(cb_data.status);
+			break;
+		}
+		break;
+
+	case 3:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				dev->state++;
+				device_xfer_control_read(&usbh_buffer[0], USB_DT_DEVICE_SIZE,
+					device_enumerate, dev);
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+				dev->state = 2;
+
+				// WARNING: Recursion
+				// .. but should work
+				cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+				device_enumerate(dev, cb_data);
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				device_enumeration_terminate(dev);
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+
+	case 4:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					struct usb_device_descriptor *ddt =
+							(struct usb_device_descriptor *)&usbh_buffer[0];
+					struct usb_setup_data setup_data;
+
+					setup_data.bmRequestType = 0b10000000;
+					setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+					setup_data.wValue = USB_DT_CONFIGURATION << 8;
+					setup_data.wIndex = 0;
+					setup_data.wLength = ddt->bMaxPacketSize0;//USB_DT_CONFIGURATION_SIZE;
+
+					dev->state++;
+					device_xfer_control_write(&setup_data, sizeof(setup_data),
+						device_enumerate, dev);
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				if (cb_data.transferred_length >= 8) {
+					struct usb_device_descriptor *ddt =
+						(struct usb_device_descriptor *)&usbh_buffer[0];
+					dev->packet_size_max0 = ddt->bMaxPacketSize0;
+					dev->state = 2;
+
+					// WARNING: Recursion
+					// .. but should work
+					cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+					device_enumerate(dev, cb_data);
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+				device_enumeration_terminate(dev);
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+	case 5:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				dev->state++;
+				device_xfer_control_read(&usbh_buffer[USB_DT_DEVICE_SIZE],
+					dev->packet_size_max0, device_enumerate, dev);
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				device_enumeration_terminate(dev);
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+
+	case 6:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					struct usb_config_descriptor *cdt =
+						(struct usb_config_descriptor *)&usbh_buffer[USB_DT_DEVICE_SIZE];
+					struct usb_setup_data setup_data;
+					LOG_PRINTF("WRITE: LEN: %d", cdt->wTotalLength);
+					setup_data.bmRequestType = 0b10000000;
+					setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+					setup_data.wValue = USB_DT_CONFIGURATION << 8;
+					setup_data.wIndex = 0;
+					setup_data.wLength = cdt->wTotalLength;
+
+					dev->state++;
+					device_xfer_control_write(&setup_data, sizeof(setup_data),
+						device_enumerate, dev);
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				if (cb_data.transferred_length >= USB_DT_CONFIGURATION_SIZE) {
+					struct usb_config_descriptor *cdt =
+						(struct usb_config_descriptor *)&usbh_buffer[USB_DT_DEVICE_SIZE];
+					if (cb_data.transferred_length <= cdt->wTotalLength) {
+						dev->state = 8;
+
+						// WARNING: Recursion
+						// .. but should work
+						cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+						device_enumerate(dev, cb_data);
+					}
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+				device_enumeration_terminate(dev);
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+
+	case 7:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					struct usb_config_descriptor *cdt =
+						(struct usb_config_descriptor *)&usbh_buffer[USB_DT_DEVICE_SIZE];
+					dev->state++;
+					device_xfer_control_read(&usbh_buffer[USB_DT_DEVICE_SIZE],
+						cdt->wTotalLength, device_enumerate, dev);
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				device_enumeration_terminate(dev);
+				ERROR(cb_data.status);
+				break;
+			}
+		}
+		break;
+
+	case 8:
+		{
+			switch (cb_data.status) {
+			case USBH_PACKET_CALLBACK_STATUS_OK:
+				{
+					struct usb_config_descriptor *cdt =
+						(struct usb_config_descriptor *)&usbh_buffer[USB_DT_DEVICE_SIZE];
+					LOG_PRINTF("TOTAL_LENGTH: %d\r\n", cdt->wTotalLength);
+					device_register(usbh_buffer, cdt->wTotalLength + USB_DT_DEVICE_SIZE, dev);
+					dev->state++;
+
+					reset_enumeration();
+				}
+				break;
+
+			case USBH_PACKET_CALLBACK_STATUS_EFATAL:
+			case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+			case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+				device_enumeration_terminate(dev);
+				ERROR(cb_data.status);
+				break;
+			}
+
+		}
+		break;
+	}
+
+	if (state_start == dev->state) {
+		LOG_PRINTF("\r\n !HANG %d\r\n", state_start);
+	}
+}
+
+void device_enumeration_start(usbh_device_t *dev)
+{
+	set_enumeration();
+	dev->state = 1;
+
+	// save address
+	uint8_t address = dev->address;
+	dev->address = 0;
+
+	if (dev->speed == USBH_SPEED_LOW) {
+		dev->packet_size_max0 = 8;
+	} else {
+		dev->packet_size_max0 = 64;
+	}
+
+	usbh_data.address_temporary = address;
+
+	LOG_PRINTF("\r\n\r\n\r\n ENUMERATION OF DEVICE@%d STARTED \r\n\r\n", address);
+
+	struct usb_setup_data setup_data;
+
+	setup_data.bmRequestType = 0b00000000;
+	setup_data.bRequest = USB_REQ_SET_ADDRESS;
+	setup_data.wValue = address;
+	setup_data.wIndex = 0;
+	setup_data.wLength = 0;
+
+	device_xfer_control_write(&setup_data, sizeof(setup_data),
+		device_enumerate, dev);
+}
+
+/**
+ * Should be called with at least 1kHz frequency
+ *
+ */
+void usbh_poll(uint32_t t_us)
+{
+	uint32_t k = 0;
+	while (usbh_data.lld_drivers[k]) {
+		usbh_device_t * usbh_device =
+			((usbh_generic_data_t *)(usbh_data.lld_drivers[k]->driver_data))->usbh_device;
+		usbh_generic_data_t *lld_data = usbh_data.lld_drivers[k]->driver_data;
+
+		enum USBH_POLL_STATUS poll_status =
+			usbh_data.lld_drivers[k]->poll(lld_data, t_us);
+
+		switch (poll_status) {
+		case USBH_POLL_STATUS_DEVICE_CONNECTED:
+			// New device found
+			LOG_PRINTF("\r\nDEVICE FOUND\r\n");
+			usbh_device[0].lld = usbh_data.lld_drivers[k];
+			usbh_device[0].speed = usbh_data.lld_drivers[k]->root_speed(lld_data);
+			usbh_device[0].address = 1;
+
+			device_enumeration_start(&usbh_device[0]);
+			break;
+
+		case USBH_POLL_STATUS_DEVICE_DISCONNECTED:
+			{
+				// Device disconnected
+				if (usbh_device[0].drv && usbh_device[0].drvdata) {
+					usbh_device[0].drv->remove(usbh_device[0].drvdata);
+				}
+				usbh_device[0].drv = 0;
+				usbh_device[0].drvdata = 0;
+
+				uint32_t i;
+				for (i = 1; i < USBH_MAX_DEVICES; i++) {
+					usbh_device[i].address = -1;
+					usbh_device[i].drv = 0;
+					usbh_device[i].drvdata = 0;
+				}
+			}
+			break;
+
+		default:
+			break;
+		}
+
+		if (lld_data->usbh_device[0].drv && usbh_device[0].drvdata) {
+			usbh_device[0].drv->poll(usbh_device[0].drvdata, t_us);
+		}
+
+		k++;
+	}
+}
+
+void usbh_read(usbh_device_t *dev, usbh_packet_t *packet)
+{
+	const usbh_driver_t *lld = dev->lld;
+	lld->read(lld->driver_data, packet);
+}
+
+void usbh_write(usbh_device_t *dev, const usbh_packet_t *packet)
+{
+	const usbh_driver_t *lld = dev->lld;
+	lld->write(lld->driver_data, packet);
+}
+
diff --git a/src/usbh_lld_stm32f4.c b/src/usbh_lld_stm32f4.c
new file mode 100644
index 0000000..6cf6015
--- /dev/null
+++ b/src/usbh_lld_stm32f4.c
@@ -0,0 +1,1048 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "usbh_lld_stm32f4.h"
+#include "usart_helpers.h"
+#include "driver/usbh_device_driver.h"
+
+#include <string.h>
+#include <stdint.h>
+#include <libopencm3/stm32/otg_hs.h>
+#include <libopencm3/stm32/otg_fs.h>
+
+
+
+/* Receive FIFO size in 32-bit words. */
+#define RX_FIFO_SIZE    (64)
+/* Transmit NON-periodic FIFO size in 32-bit words. */
+#define TX_NP_FIFO_SIZE (64)
+/* Transmit periodic FIFO size in 32-bit words. */
+#define TX_P_FIFO_SIZE  (64)
+
+
+
+
+enum CHANNEL_STATE {
+	CHANNEL_STATE_FREE = 0,
+	CHANNEL_STATE_WORK = 1
+};
+
+struct _channel {
+	enum CHANNEL_STATE state;
+	usbh_packet_t packet;
+	uint32_t data_index; //used in receive function
+	uint8_t error_count;
+};
+typedef struct _channel channel_t;
+
+enum DEVICE_STATE {
+	DEVICE_STATE_INIT = 0,
+	DEVICE_STATE_RUN = 1,
+	DEVICE_STATE_RESET = 2
+};
+
+enum DEVICE_POLL_STATE {
+	DEVICE_POLL_STATE_DISCONN = 0,
+	DEVICE_POLL_STATE_DEVCONN = 1,
+	DEVICE_POLL_STATE_DEVRST = 2,
+	DEVICE_POLL_STATE_RUN = 3
+};
+
+struct _usbh_lld_stm32f4_driver_data {
+	usbh_generic_data_t generic;
+	const uint32_t base;
+	channel_t *channels;
+	const uint8_t num_channels;
+
+	uint32_t poll_sequence;
+	enum DEVICE_POLL_STATE dpstate;
+	enum DEVICE_STATE state;
+	uint32_t state_prev;//for reset only
+	uint32_t time_curr_us;
+	uint32_t timestamp_us;
+};
+typedef struct _usbh_lld_stm32f4_driver_data usbh_lld_stm32f4_driver_data_t;
+
+
+
+/*
+ * Define correct REBASE. If only one driver is enabled use directly OTG base
+ *
+ */
+#if 	defined(USE_STM32F4_USBH_DRIVER_FS) || \
+		defined(USE_STM32F4_USBH_DRIVER_HS)
+
+#if 	defined(USE_STM32F4_USBH_DRIVER_FS) && \
+		defined(USE_STM32F4_USBH_DRIVER_HS)
+#define REBASE(reg)				MMIO32(dev->base + reg)
+#define REBASE_CH(reg, x)	MMIO32(dev->base + reg(x))
+#elif defined(USE_STM32F4_USBH_DRIVER_FS)
+#define REBASE(reg)				MMIO32(USB_OTG_FS_BASE + reg)
+#define REBASE_CH(reg, x)	MMIO32(USB_OTG_FS_BASE + reg(x))
+#elif defined(USE_STM32F4_USBH_DRIVER_HS)
+#define REBASE(reg)				MMIO32(USB_OTG_HS_BASE + reg)
+#define REBASE_CH(reg, x)	MMIO32(USB_OTG_HS_BASE + reg(x))
+#endif
+
+
+static void stm32f4_usbh_init(void *drvdata);
+static enum USBH_POLL_STATUS stm32f4_usbh_poll(void *drvdata, uint32_t time_curr_us);
+static void stm32f4_usbh_read(void *drvdata, usbh_packet_t *packet);
+static void stm32f4_usbh_write(void *drvdata, const usbh_packet_t *packet);
+uint8_t stm32f4_root_speed(void *drvdata);
+
+static int8_t get_free_channel(void *drvdata);
+static void channels_init(void *drvdata);
+static void rxflvl_handle(void *drvdata);
+static void free_channel(void *drvdata, uint8_t channel);
+
+// USB Full Speed - OTG_FS
+#if defined(USE_STM32F4_USBH_DRIVER_FS)
+#define NUM_CHANNELS_FS		(8)
+static channel_t channels_fs[NUM_CHANNELS_FS];
+static usbh_lld_stm32f4_driver_data_t driver_data_fs = {
+	.base = USB_OTG_FS_BASE,
+	.channels = channels_fs,
+	.num_channels = NUM_CHANNELS_FS
+};
+const usbh_driver_t stm32f4_usbh_driver_fs = {
+	.init = stm32f4_usbh_init,
+	.poll = stm32f4_usbh_poll,
+	.read = stm32f4_usbh_read,
+	.write = stm32f4_usbh_write,
+	.root_speed = stm32f4_root_speed,
+	.driver_data = &driver_data_fs
+};
+#endif
+
+// USB High Speed - OTG_HS
+#if defined(USE_STM32F4_USBH_DRIVER_HS)
+#define NUM_CHANNELS_HS		(12)
+static channel_t channels_hs[NUM_CHANNELS_HS];
+static usbh_lld_stm32f4_driver_data_t driver_data_hs = {
+	.base = USB_OTG_HS_BASE,
+	.channels = channels_hs,
+	.num_channels = NUM_CHANNELS_HS
+};
+const usbh_driver_t stm32f4_usbh_driver_hs = {
+	.init = stm32f4_usbh_init,
+	.poll = stm32f4_usbh_poll,
+	.read = stm32f4_usbh_read,
+	.write = stm32f4_usbh_write,
+	.root_speed = stm32f4_root_speed,
+	.driver_data = &driver_data_hs
+};
+#endif
+
+
+
+static inline void reset_start(usbh_lld_stm32f4_driver_data_t *dev)
+{
+
+	// apply reset condition on port
+	REBASE(OTG_HPRT) |= OTG_HPRT_PRST;
+
+	// push current state to stack
+	dev->state_prev = dev->state;
+
+	// move to new state
+	dev->state = DEVICE_STATE_RESET;
+
+	// schedule disable reset condition after ~10ms
+	dev->timestamp_us = dev->time_curr_us;
+}
+
+/**
+ * Should be nonblocking
+ *
+ */
+static void stm32f4_usbh_init(void *drvdata)
+{
+	usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+	dev->state = DEVICE_STATE_INIT;
+	dev->poll_sequence = 0;
+	dev->timestamp_us = dev->time_curr_us;
+
+	//Disable interrupts first
+	REBASE(OTG_GAHBCFG) &= ~OTG_GAHBCFG_GINT;
+
+	// Select full speed phy
+	REBASE(OTG_GUSBCFG) |= OTG_GUSBCFG_PHYSEL;
+}
+
+static void stm32f4_usbh_port_channel_setup(
+	void *drvdata, uint32_t channel, uint32_t address,
+	uint32_t eptyp, uint32_t epnum, uint32_t epdir,
+	uint32_t max_packet_size)
+{
+	usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+	channel_t *channels = dev->channels;
+
+	// TODO: maybe to function
+	switch (eptyp) {
+	case USBH_EPTYP_CONTROL:
+		eptyp = OTG_HCCHAR_EPTYP_CONTROL;
+		break;
+	case USBH_EPTYP_BULK:
+		eptyp = OTG_HCCHAR_EPTYP_BULK;
+		break;
+	case USBH_EPTYP_INTERRUPT:
+		eptyp = OTG_HCCHAR_EPTYP_INTERRUPT;
+		break;
+	case USBH_EPTYP_ISOCHRONOUS:
+		eptyp = OTG_HCCHAR_EPTYP_ISOCHRONOUS;
+		break;
+	}
+
+	uint32_t speed = 0;
+	if (channels[channel].packet.speed == USBH_SPEED_LOW) {
+		speed = OTG_HCCHAR_LSDEV;
+	}
+
+	REBASE_CH(OTG_HCCHAR, channel) = OTG_HCCHAR_CHENA |
+				(OTG_HCCHAR_DAD_MASK & (address << 22)) |
+				OTG_HCCHAR_MCNT_1 |
+				(OTG_HCCHAR_EPTYP_MASK & (eptyp << 18)) |
+				(speed) |
+				(epdir) |
+				(OTG_HCCHAR_EPNUM_MASK & (epnum << 11) )|
+				(OTG_HCCHAR_MPSIZ_MASK & max_packet_size);
+
+}
+
+
+/**
+ * TODO: Check for maximum datalength
+ */
+static void stm32f4_usbh_read(void *drvdata, usbh_packet_t *packet)
+{
+	usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+	channel_t *channels = dev->channels;
+
+	int8_t channel = get_free_channel(dev);
+	if (channel == -1) {
+		// BIG PROBLEM
+		LOG_PRINTF("FATAL ERROR IN, NO CHANNEL LEFT \r\n");
+		usbh_packet_callback_data_t cb_data;
+		cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+		cb_data.transferred_length = 0;
+		packet->callback(packet->callback_arg, cb_data);
+		return;
+	}
+
+	channels[channel].data_index = 0;
+	channels[channel].packet = *packet;
+
+	uint32_t dpid;
+	if (packet->toggle[0]) {
+		dpid = OTG_HCTSIZ_DPID_DATA1;
+	} else {
+		dpid = OTG_HCTSIZ_DPID_DATA0;
+	}
+
+	uint32_t num_packets;
+	if (packet->datalen) {
+		num_packets = ((packet->datalen - 1) / packet->endpoint_size_max) + 1;
+	} else {
+		num_packets = 0;
+	}
+
+	REBASE_CH(OTG_HCTSIZ, channel) = dpid | (num_packets << 19) | packet->datalen;
+
+	stm32f4_usbh_port_channel_setup(dev, channel,
+									packet->address,
+									packet->endpoint_type,
+									packet->endpoint_address,
+									OTG_HCCHAR_EPDIR_IN,
+									packet->endpoint_size_max);
+}
+
+/**
+ *
+ * 	Bug: datalen > max_packet_size ...
+ */
+static void stm32f4_usbh_write(void *drvdata, const usbh_packet_t *packet)
+{
+	usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+	channel_t *channels = dev->channels;
+
+	int8_t channel = get_free_channel(dev);
+
+	if (channel == -1) {
+		// BIG PROBLEM
+		LOG_PRINTF("FATAL ERROR OUT, NO CHANNEL LEFT \r\n");
+		usbh_packet_callback_data_t cb_data;
+		cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+		cb_data.transferred_length = 0;
+		packet->callback(packet->callback_arg, cb_data);
+		return;
+	}
+
+	channels[channel].data_index = 0;
+	channels[channel].packet = *packet;
+
+	uint32_t dpid;
+	if (packet->endpoint_type == USBH_EPTYP_CONTROL) {
+		dpid = OTG_HCTSIZ_DPID_MDATA;
+		packet->toggle[0] = 0;
+	} else if(packet->endpoint_type == USBH_EPTYP_INTERRUPT) {
+		if (packet->toggle[0]) {
+			dpid = OTG_HCTSIZ_DPID_DATA1;
+		} else {
+			dpid = OTG_HCTSIZ_DPID_DATA0;
+		}
+	} else if (packet->endpoint_type == USBH_EPTYP_BULK) {
+		if (packet->toggle[0]) {
+			dpid = OTG_HCTSIZ_DPID_DATA1;
+		} else {
+			dpid = OTG_HCTSIZ_DPID_DATA0;
+		}
+	} else {
+		dpid = OTG_HCTSIZ_DPID_DATA0; // ! TODO: BUG
+		LOG_PRINTF("BUG, %d",__LINE__);
+	}
+
+	uint32_t num_packets;
+	if (packet->datalen) {
+		num_packets = ((packet->datalen - 1) / packet->endpoint_size_max) + 1;
+	} else {
+		num_packets = 1;
+	}
+	REBASE_CH(OTG_HCTSIZ, channel) = dpid | (num_packets << 19) | packet->datalen;
+
+	stm32f4_usbh_port_channel_setup(dev, channel,
+									packet->address,
+									packet->endpoint_type,
+									packet->endpoint_address,
+									OTG_HCCHAR_EPDIR_OUT,
+									packet->endpoint_size_max);
+
+	if (packet->endpoint_type == USBH_EPTYP_CONTROL ||
+		packet->endpoint_type == USBH_EPTYP_BULK) {
+
+		volatile uint32_t *fifo = &REBASE_CH(OTG_FIFO, channel) + RX_FIFO_SIZE;
+		const uint32_t * buf32 = packet->data;
+		uint32_t i;
+		for(i = packet->datalen; i > 0; i-=4) {
+			*fifo++ = *buf32++;
+		}
+	} else {
+		volatile uint32_t *fifo = &REBASE_CH(OTG_FIFO, channel) +
+			RX_FIFO_SIZE + TX_NP_FIFO_SIZE;
+		const uint32_t * buf32 = packet->data;
+		uint32_t i;
+		for(i = packet->datalen; i > 0; i-=4) {
+			*fifo++ = *buf32++;
+		}
+	}
+	LOG_PRINTF("->WRITE %08X\r\n", REBASE_CH(OTG_HCCHAR, channel));
+}
+
+static void rxflvl_handle(void *drvdata)
+{
+	usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+	channel_t *channels = dev->channels;
+	uint32_t rxstsp = REBASE(OTG_GRXSTSP);
+	uint8_t channel = rxstsp&0xf;
+	uint32_t len = (rxstsp>>4) & 0x1ff;
+	if ((rxstsp&OTG_GRXSTSP_PKTSTS_MASK) == OTG_GRXSTSP_PKTSTS_IN) {
+		uint8_t *data = channels[channel].packet.data;
+		uint32_t *buf32 = (uint32_t *)&data[channels[channel].data_index];
+
+		int32_t i;
+		uint32_t extra;
+		if (!len) {
+			return;
+		}
+		// Receive data from fifo
+		volatile uint32_t *fifo = &REBASE_CH(OTG_FIFO, channel);
+		for (i = len; i > 4; i -= 4) {
+			*buf32++ = *fifo++;
+		}
+		extra = *fifo;
+
+		memcpy(buf32, &extra, i);
+		channels[channel].data_index += len;
+
+		// If transfer not complete, Enable channel to continue
+		if ( channels[channel].data_index < channels[channel].packet.datalen) {
+			if (len == channels[channel].packet.endpoint_size_max) {
+				REBASE_CH(OTG_HCCHAR, channel) |= OTG_HCCHAR_CHENA;
+				LOG_PRINTF("CHENA[%d/%d] ", channels[channel].data_index, channels[channel].packet.datalen);
+			}
+
+		}
+
+	} else if ((rxstsp&OTG_GRXSTSP_PKTSTS_MASK) == OTG_GRXSTSP_PKTSTS_IN_COMP) {
+#ifdef USART_DEBUG
+		uint32_t i;
+		LOG_PRINTF("\r\nDATA: ");
+		for (i = 0; i < channels[channel].data_index; i++) {
+			uint8_t *data = channels[channel].packet.data;
+			LOG_PRINTF("%02X ", data[i]);
+		}
+#endif
+	} else if ((rxstsp&OTG_GRXSTSP_PKTSTS_MASK) == OTG_GRXSTSP_PKTSTS_CHH) {
+
+	} else {
+
+	}
+}
+
+
+static enum USBH_POLL_STATUS poll_run(usbh_lld_stm32f4_driver_data_t *dev)
+{
+	channel_t *channels = dev->channels;
+
+	if (dev->dpstate == DEVICE_POLL_STATE_DISCONN) {
+		REBASE(OTG_GINTSTS) = REBASE(OTG_GINTSTS);
+		// Check for connection of device
+		if ((REBASE(OTG_HPRT) & OTG_HPRT_PCDET)  &&
+			(REBASE(OTG_HPRT) & OTG_HPRT_PCSTS) ) {
+
+			dev->dpstate = DEVICE_POLL_STATE_DEVCONN;
+			dev->timestamp_us = dev->time_curr_us;
+			return USBH_POLL_STATUS_NONE;
+		}
+	}
+
+	if (dev->dpstate == DEVICE_POLL_STATE_DEVCONN) {
+		// May be other condition, e.g. Debounce done,
+		// using 0.5s wait by default
+		if (dev->time_curr_us - dev->timestamp_us < 500000) {
+			return USBH_POLL_STATUS_NONE;
+		}
+
+		if ((REBASE(OTG_HPRT) & OTG_HPRT_PCDET)  &&
+			(REBASE(OTG_HPRT) & OTG_HPRT_PCSTS) ) {
+			if ((REBASE(OTG_HPRT) & OTG_HPRT_PSPD_MASK) == OTG_HPRT_PSPD_FULL) {
+				REBASE(OTG_HFIR) = (REBASE(OTG_HFIR) & ~OTG_HFIR_FRIVL_MASK) | 48000;
+				if ((REBASE(OTG_HCFG) & OTG_HCFG_FSLSPCS_MASK) != OTG_HCFG_FSLSPCS_48MHz) {
+					REBASE(OTG_HCFG) = (REBASE(OTG_HCFG) & ~OTG_HCFG_FSLSPCS_MASK) | OTG_HCFG_FSLSPCS_48MHz;
+					LOG_PRINTF("\r\n Reset Full-Speed \r\n");
+				}
+				channels_init(dev);
+				dev->dpstate = DEVICE_POLL_STATE_DEVRST;
+				reset_start(dev);
+
+			} else if ((REBASE(OTG_HPRT) & OTG_HPRT_PSPD_MASK) == OTG_HPRT_PSPD_LOW) {
+				REBASE(OTG_HFIR) = (REBASE(OTG_HFIR) & ~OTG_HFIR_FRIVL_MASK) | 6000;
+				if ((REBASE(OTG_HCFG) & OTG_HCFG_FSLSPCS_MASK) != OTG_HCFG_FSLSPCS_6MHz) {
+					REBASE(OTG_HCFG) = (REBASE(OTG_HCFG) & ~OTG_HCFG_FSLSPCS_MASK) | OTG_HCFG_FSLSPCS_6MHz;
+					LOG_PRINTF("\r\n Reset Low-Speed \r\n");
+				}
+
+				channels_init(dev);
+				dev->dpstate = DEVICE_POLL_STATE_DEVRST;
+				reset_start(dev);
+			}
+			return USBH_POLL_STATUS_NONE;
+		}
+	}
+
+	if (dev->dpstate == DEVICE_POLL_STATE_DEVRST) {
+		if (dev->time_curr_us - dev->timestamp_us < 210000) {
+			return USBH_POLL_STATUS_NONE;
+		} else {
+			dev->dpstate = DEVICE_POLL_STATE_RUN;
+		}
+	}
+
+	// ELSE RUN
+
+	if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_SOF) {
+		REBASE(OTG_GINTSTS) = OTG_GINTSTS_SOF;
+	}
+
+	while (REBASE(OTG_GINTSTS) & OTG_GINTSTS_RXFLVL) {
+		//receive data
+		rxflvl_handle(dev);
+	}
+
+	if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_HPRTINT) {
+		if (REBASE(OTG_HPRT) & OTG_HPRT_PENCHNG) {
+			uint32_t hprt = REBASE(OTG_HPRT);
+			// Clear Interrupt
+			// HARDWARE BUG - not mentioned in errata
+			// To clear interrupt write 0 to PENA
+			// To disable port write 1 to PENCHNG
+			REBASE(OTG_HPRT) &= ~OTG_HPRT_PENA;
+			LOG_PRINTF("PENCHNG");
+			if ((hprt & OTG_HPRT_PENA)) {
+				return USBH_POLL_STATUS_DEVICE_CONNECTED;
+			}
+
+		}
+
+		if (REBASE(OTG_HPRT) & OTG_HPRT_POCCHNG) {
+			// TODO: Check for functionality
+			REBASE(OTG_HPRT) |= OTG_HPRT_POCCHNG;
+			LOG_PRINTF("POCCHNG");
+		}
+	}
+
+	if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_DISCINT) {
+		REBASE(OTG_GINTSTS) = OTG_GINTSTS_DISCINT;
+		LOG_PRINTF("DISCINT");
+
+		/*
+		 * When the voltage drops, DISCINT interrupt is generated although
+		 * Device is connected, so there is no need to reinitialize channels.
+		 * Often, DISCINT is bad interpreted upon insertion of device
+		 */
+		if (!(REBASE(OTG_HPRT) & OTG_HPRT_PCSTS)) {
+			LOG_PRINTF("discint processsing...");
+			channels_init(dev);
+		}
+		REBASE(OTG_GINTSTS) = REBASE(OTG_GINTSTS);
+		dev->dpstate = DEVICE_POLL_STATE_DISCONN;
+		return USBH_POLL_STATUS_DEVICE_DISCONNECTED;
+	}
+
+	if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_HCINT) {
+		uint32_t channel;
+
+		for(channel = 0; channel < dev->num_channels; channel++)
+		{
+			if (channels[channel].state != CHANNEL_STATE_WORK ||
+				!(REBASE(OTG_HAINT)&(1<<channel))) {
+				continue;
+			}
+			uint32_t hcint = REBASE_CH(OTG_HCINT, channel);
+			uint8_t eptyp = channels[channel].packet.endpoint_type;
+
+			// Write
+			if (!(REBASE_CH(OTG_HCCHAR, channel)&OTG_HCCHAR_EPDIR_IN)) {
+
+				if (hcint & OTG_HCINT_NAK) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_NAK;
+					LOG_PRINTF("NAK");
+
+					REBASE_CH(OTG_HCCHAR, channel) |= OTG_HCCHAR_CHENA;
+
+				}
+
+				if (hcint & OTG_HCINT_ACK) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_ACK;
+					LOG_PRINTF("ACK");
+					if (eptyp == USBH_EPTYP_CONTROL) {
+						channels[channel].packet.toggle[0] = 1;
+					} else {
+						channels[channel].packet.toggle[0] ^= 1;
+					}
+				}
+
+				if (hcint & OTG_HCINT_XFRC) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_XFRC;
+					LOG_PRINTF("XFRC");
+
+					free_channel(dev, channel);
+
+					usbh_packet_callback_data_t cb_data;
+					cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+					cb_data.transferred_length = channels[channel].data_index;
+
+					channels[channel].packet.callback(
+						channels[channel].packet.callback_arg,
+						cb_data);
+					continue;
+				}
+
+				if (hcint & OTG_HCINT_FRMOR) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_FRMOR;
+					LOG_PRINTF("FRMOR");
+
+					usbh_packet_callback_data_t cb_data;
+					cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+					cb_data.transferred_length = 0;
+
+					channels[channel].packet.callback(
+						channels[channel].packet.callback_arg,
+						cb_data);
+					free_channel(dev, channel);
+				}
+
+				if (hcint & OTG_HCINT_TXERR) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_TXERR;
+					LOG_PRINTF("TXERR");
+
+					free_channel(dev, channel);
+
+					usbh_packet_callback_data_t cb_data;
+					cb_data.status = USBH_PACKET_CALLBACK_STATUS_EAGAIN;
+					cb_data.transferred_length = 0;
+
+					channels[channel].packet.callback(
+						channels[channel].packet.callback_arg,
+						cb_data);
+
+
+				}
+
+				if (hcint & OTG_HCINT_STALL) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_STALL;
+					LOG_PRINTF("STALL");
+
+					free_channel(dev, channel);
+
+					usbh_packet_callback_data_t cb_data;
+					cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+					cb_data.transferred_length = 0;
+
+
+					channels[channel].packet.callback(
+						channels[channel].packet.callback_arg,
+						cb_data);
+				}
+
+				if (hcint & OTG_HCINT_CHH) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_CHH;
+					LOG_PRINTF("CHH");
+
+					free_channel(dev, channel);
+				}
+			} else { // Read
+
+				if (hcint & OTG_HCINT_NAK) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_NAK;
+					if (eptyp == USBH_EPTYP_CONTROL) {
+						 LOG_PRINTF("NAK");
+					}
+
+					REBASE_CH(OTG_HCCHAR, channel) |= OTG_HCCHAR_CHENA;
+
+				}
+
+				if (hcint & OTG_HCINT_DTERR) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_DTERR;
+					LOG_PRINTF("DTERR");
+				}
+
+				if (hcint & OTG_HCINT_ACK) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_ACK;
+					LOG_PRINTF("ACK");
+
+					channels[channel].packet.toggle[0] ^= 1;
+
+				}
+
+
+
+				if (hcint & OTG_HCINT_XFRC) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_XFRC;
+					LOG_PRINTF("XFRC");
+
+					free_channel(dev, channel);
+					usbh_packet_callback_data_t cb_data;
+					if (channels[channel].data_index == channels[channel].packet.datalen) {
+						cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+					} else {
+						cb_data.status = USBH_PACKET_CALLBACK_STATUS_ERRSIZ;
+					}
+					cb_data.transferred_length = channels[channel].data_index;
+
+					channels[channel].packet.callback(
+						channels[channel].packet.callback_arg,
+						cb_data);
+
+					continue;
+				}
+
+				if (hcint & OTG_HCINT_BBERR) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_BBERR;
+					LOG_PRINTF("BBERR");
+					free_channel(dev, channel);
+
+					usbh_packet_callback_data_t cb_data;
+					cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+					cb_data.transferred_length = 0;
+
+					channels[channel].packet.callback(
+						channels[channel].packet.callback_arg,
+						cb_data);
+				}
+
+				if (hcint & OTG_HCINT_FRMOR) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_FRMOR;
+					LOG_PRINTF("FRMOR");
+
+				}
+
+				if (hcint & OTG_HCINT_TXERR) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_TXERR;
+					LOG_PRINTF("TXERR");
+
+					free_channel(dev, channel);
+
+					usbh_packet_callback_data_t cb_data;
+					cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+					cb_data.transferred_length = 0;
+
+					channels[channel].packet.callback(
+						channels[channel].packet.callback_arg,
+						cb_data);
+
+				}
+
+				if (hcint & OTG_HCINT_STALL) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_STALL;
+					LOG_PRINTF("STALL");
+
+					free_channel(dev, channel);
+
+					usbh_packet_callback_data_t cb_data;
+					cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+					cb_data.transferred_length = 0;
+
+					channels[channel].packet.callback(
+						channels[channel].packet.callback_arg,
+						cb_data);
+
+				}
+				if (hcint & OTG_HCINT_CHH) {
+					REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_CHH;
+					LOG_PRINTF("CHH");
+					free_channel(dev, channel);
+				}
+
+			}
+		}
+	}
+
+	if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_MMIS) {
+		REBASE(OTG_GINTSTS) = OTG_GINTSTS_MMIS;
+		LOG_PRINTF("Mode mismatch");
+	}
+
+	if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_IPXFR) {
+		REBASE(OTG_GINTSTS) = OTG_GINTSTS_IPXFR;
+		LOG_PRINTF("IPXFR");
+	}
+
+	return USBH_POLL_STATUS_NONE;
+}
+
+/*
+ * Sequence numbers are hardcoded, since it is used
+ * locally in poll_init() function.
+ * If value of poll_sequence is needed elsewhere, enum must be defined.
+ *
+ */
+static void poll_init(usbh_lld_stm32f4_driver_data_t *dev)
+{
+	//=======================================
+
+	int done = 0;
+	/* Wait for AHB idle. */
+	switch (dev->poll_sequence) {
+	case 0:// wait until AHBIDL is set
+		if (REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_AHBIDL) {
+			done = 1;
+		}
+		break;
+
+	case 1:// wait 1ms and issue core soft reset
+
+		// needs delay to not hang?? Do not know why.
+		// Maybe after AHBIDL is set, it needs to set up some things
+		if (dev->time_curr_us - dev->timestamp_us > 1000) {
+			REBASE(OTG_GRSTCTL) |= OTG_GRSTCTL_CSRST;
+			done = 1;
+		}
+		break;
+
+	case 2:// wait until core soft reset processing is done
+		if (!(REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_CSRST)) {
+			done = 1;
+		}
+		break;
+
+	case 3:// wait for 50ms
+		if (dev->time_curr_us - dev->timestamp_us > 50000) {
+			done = 1;
+		}
+		break;
+
+	case 4:// wait until AHBIDL is set and power up the USB
+		if (REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_AHBIDL) {
+			REBASE(OTG_GCCFG) = OTG_GCCFG_VBUSASEN | OTG_GCCFG_VBUSBSEN |
+					OTG_GCCFG_NOVBUSSENS | OTG_GCCFG_PWRDWN;
+			done = 1;
+		}
+		break;
+
+	case 5:// wait for 50ms and force host only mode
+		if (dev->time_curr_us - dev->timestamp_us > 50000) {
+
+			// Core initialized
+			// Force host only mode.
+			REBASE(OTG_GUSBCFG) |= OTG_GUSBCFG_FHMOD;
+			done = 1;
+		}
+		break;
+
+	case 6:// wait for 200ms and reset PHY clock start reset processing
+		if (dev->time_curr_us - dev->timestamp_us > 200000) {
+			/* Restart the PHY clock. */
+			REBASE(OTG_PCGCCTL) = 0;
+
+			REBASE(OTG_HCFG) = 	(REBASE(OTG_HCFG) & ~OTG_HCFG_FSLSPCS_MASK) |
+							OTG_HCFG_FSLSPCS_48MHz;
+
+			// Start reset processing
+			REBASE(OTG_HPRT) |= OTG_HPRT_PRST;
+
+			done = 1;
+
+		}
+		break;
+
+	case 7:// wait for reset processing to be done(12ms), disable PRST
+		if (dev->time_curr_us - dev->timestamp_us > 12000) {
+
+			REBASE(OTG_HPRT) &= ~OTG_HPRT_PRST;
+			done = 1;
+		}
+		break;
+
+	case 8:// wait 12ms after PRST was disabled, configure fifo
+		if (dev->time_curr_us - dev->timestamp_us > 12000) {
+
+			REBASE(OTG_HCFG) &= ~OTG_HCFG_FSLSS;
+
+			REBASE(OTG_GRXFSIZ) = RX_FIFO_SIZE;
+			REBASE(OTG_GNPTXFSIZ) = (TX_NP_FIFO_SIZE << 16) |
+								RX_FIFO_SIZE;
+			REBASE(OTG_HPTXFSIZ) = (TX_P_FIFO_SIZE << 16) |
+								(RX_FIFO_SIZE +	TX_NP_FIFO_SIZE);
+
+			// FLUSH RX FIFO
+			REBASE(OTG_GRSTCTL) |= OTG_GRSTCTL_RXFFLSH;
+
+			done = 1;
+		}
+		break;
+
+	case 9: // wait to RX FIFO become flushed, flush TX
+		if (!(REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_RXFFLSH)) {
+			REBASE(OTG_GRSTCTL) |= OTG_GRSTCTL_TXFFLSH | (0x10 << 6);
+
+			done = 1;
+		}
+		break;
+
+	case 10: // wait to TX FIFO become flushed
+		if (!(REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_TXFFLSH)) {
+
+			channels_init(dev);
+
+			REBASE(OTG_GOTGINT) |= 1 << 19;
+			REBASE(OTG_GINTMSK) = 0;
+			REBASE(OTG_GINTSTS) = ~0;
+			REBASE(OTG_HPRT) |= OTG_HPRT_PPWR;
+
+			done = 1;
+		}
+		break;
+
+	case 11: // wait 200ms
+		if (dev->time_curr_us - dev->timestamp_us > 200000) {
+
+			// Uncomment to enable Interrupt generation
+			REBASE(OTG_GAHBCFG) |= OTG_GAHBCFG_GINT;
+
+			LOG_PRINTF("INIT COMPLETE\r\n");
+
+			// Finish
+			dev->state = DEVICE_STATE_RUN;
+			dev->dpstate = DEVICE_POLL_STATE_DISCONN;
+
+			done = 1;
+		}
+	}
+
+	if (done) {
+		dev->poll_sequence++;
+		dev->timestamp_us = dev->time_curr_us;
+		LOG_PRINTF("\t\t POLL SEQUENCE %d\r\n", dev->poll_sequence);
+	}
+
+}
+
+static void poll_reset(usbh_lld_stm32f4_driver_data_t *dev)
+{
+	if (dev->time_curr_us - dev->timestamp_us > 10000) {
+		REBASE(OTG_HPRT) &= ~OTG_HPRT_PRST;
+		dev->state = dev->state_prev;
+		dev->state_prev = DEVICE_STATE_RESET;
+
+		LOG_PRINTF("RESET");
+	} else {
+		LOG_PRINTF("waiting %d < %d\r\n",dev->time_curr_us, dev->timestamp_us);
+	}
+}
+
+static enum USBH_POLL_STATUS stm32f4_usbh_poll(void *drvdata, uint32_t time_curr_us)
+{
+
+	(void)time_curr_us;
+	usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+	uint32_t ret = ret = USBH_POLL_STATUS_NONE;
+
+	// TODO: Check overflow case
+	dev->time_curr_us = time_curr_us;
+
+	switch (dev->state) {
+	case DEVICE_STATE_RUN:
+		ret = poll_run(dev);
+		break;
+
+	case DEVICE_STATE_INIT:
+		poll_init(dev);
+		break;
+
+	case DEVICE_STATE_RESET:
+		poll_reset(dev);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+
+}
+
+
+/**
+ *
+ * Returns positive free channel id
+ * 	otherwise -1 for error
+ */
+static int8_t get_free_channel(void *drvdata)
+{
+	usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+	channel_t *channels = dev->channels;
+	uint32_t i = 0;
+	for (i = 0; i < dev->num_channels; i++) {
+		if (dev->channels[i].state == CHANNEL_STATE_FREE &&
+			!(REBASE_CH(OTG_HCCHAR, i) & OTG_HCCHAR_CHENA)) {
+			channels[i].state = CHANNEL_STATE_WORK;
+			REBASE_CH(OTG_HCINT, i) = ~0;
+			REBASE_CH(OTG_HCINTMSK, i) |= OTG_HCINTMSK_ACKM | OTG_HCINTMSK_NAKM |
+				OTG_HCINTMSK_TXERRM | OTG_HCINTMSK_XFRCM |
+				OTG_HCINTMSK_DTERRM | OTG_HCINTMSK_BBERRM |
+				OTG_HCINTMSK_CHHM | OTG_HCINTMSK_STALLM |
+				OTG_HCINTMSK_FRMORM;
+			REBASE(OTG_HAINTMSK) |= (1 << i);
+			dev->channels[i].error_count = 0;
+			return i;
+		}
+	}
+	return -1;
+}
+
+/*
+ * Do not clear callback and callback data, so channel can be freed even before callback is called
+ * This saves number of active channels: When one transfer ends, in callback driver can write/read to this channel again (indirectly)
+ */
+static void free_channel(void *drvdata, uint8_t channel)
+{
+	usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+	channel_t *channels = dev->channels;
+
+	if (REBASE_CH(OTG_HCCHAR, channel) & OTG_HCCHAR_CHENA) {
+		REBASE_CH(OTG_HCCHAR, channel) |= OTG_HCCHAR_CHDIS;
+		REBASE_CH(OTG_HCINT, channel) = ~0;
+		LOG_PRINTF("\r\nDisabling channel %d\r\n", channel);
+	} else {
+		channels[channel].state = CHANNEL_STATE_FREE;
+	}
+}
+/**
+ * Init channels
+ */
+static void channels_init(void *drvdata)
+{
+	usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+
+	uint32_t i = 0;
+	for (i = 0; i < dev->num_channels; i++) {
+		REBASE_CH(OTG_HCINT, i) = ~0;
+		REBASE_CH(OTG_HCINTMSK, i) = 0x7ff;
+		free_channel(dev, i);
+	}
+
+	// Enable interrupt mask bits for all channels
+	REBASE(OTG_HAINTMSK) = (1 << dev->num_channels) - 1;
+}
+
+/**
+ * Get speed of connected device
+ *
+ */
+uint8_t stm32f4_root_speed(void *drvdata)
+{
+	usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+	(void)dev;
+	uint32_t hprt_speed = REBASE(OTG_HPRT) & OTG_HPRT_PSPD_MASK;
+	if (hprt_speed == OTG_HPRT_PSPD_LOW) {
+		return USBH_SPEED_LOW;
+	} else if(hprt_speed == OTG_HPRT_PSPD_FULL) {
+		return USBH_SPEED_FULL;
+	} else if(hprt_speed == OTG_HPRT_PSPD_HIGH) {
+		return USBH_SPEED_HIGH;
+	} else {
+		// Should not happen(let the compiler be happy)
+		return USBH_SPEED_FULL;
+	}
+}
+#endif // if defined otg_hs or otg_fs
+
+
+#ifdef USART_DEBUG
+
+/**
+ * Just for debug
+ */
+void print_channels(const void *lld)
+{
+	usbh_lld_stm32f4_driver_data_t *dev = ((usbh_driver_t *)lld)->driver_data;
+	channel_t *channels = dev->channels;
+	int32_t i;
+	LOG_PRINTF("\r\nCHANNELS: \r\n");
+	for (i = 0;i < dev->num_channels;i++) {
+		LOG_PRINTF("%4d %4d %4d %08X\r\n", channels[i].state, channels[i].packet.address, channels[i].packet.datalen, MMIO32(dev->base + OTG_HCINT(i)));
+	}
+}
+#endif
+
+
+const void *usbh_lld_stm32f4_drivers[] = {
+#if defined(USE_STM32F4_USBH_DRIVER_FS)
+	&stm32f4_usbh_driver_fs,
+#endif
+
+#if defined(USE_STM32F4_USBH_DRIVER_HS)
+	&stm32f4_usbh_driver_hs,
+#endif
+	0
+};
-- 
cgit