From 7acc6fe474766788687a5257be21ac549bed77f3 Mon Sep 17 00:00:00 2001 From: Amir Hammad 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 --- 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 + * + * + * 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 . + * + */ + +#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 +#include +#include +#include +#include +#include + +#include +#include +#include + +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 + * + * + * 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 . + * + */ + + +#include "usart_helpers.h" + +#include +#include +#include +#include +#include +#include + + +#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 + * + * + * 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 . + * + */ + +#ifndef USBH_USART_HELPERS_H +#define USBH_USART_HELPERS_H + +#include "usbh_hubbed.h" +#include +#include + +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 + * + * + * 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 . + * + */ + + +#include "usart_helpers.h" +#include "usbh_driver_gp_xbox.h" +#include "driver/usbh_device_driver.h" + +#include +#include + +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 + * + * + * 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 . + * + */ + +#include "usbh_hubbed.h" +#include "driver/usbh_device_driver.h" +#include "usbh_driver_hid_mouse.h" +#include "usart_helpers.h" + +#include + +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 + + + +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 + * + * + * 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 . + * + */ + +#include "usbh_driver_hub_private.h" +#include "driver/usbh_device_driver.h" +#include "usart_helpers.h" +#include "usbh_config.h" + +#include + + +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<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<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<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<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<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 + * + * + * 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 . + * + */ + +#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 +#include +#include + + +// # 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 + * + * + * 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 . + * + */ + +#include "usbh_config.h" +#include "usbh_lld_stm32f4.h" +#include "driver/usbh_device_driver.h" +#include "usart_helpers.h" + +#include +#include + +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 + * + * + * 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 . + * + */ + +#include "usbh_lld_stm32f4.h" +#include "usart_helpers.h" +#include "driver/usbh_device_driver.h" + +#include +#include +#include +#include + + + +/* 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<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