diff options
Diffstat (limited to 'chip/stm32')
-rw-r--r-- | chip/stm32/build.mk | 1 | ||||
-rw-r--r-- | chip/stm32/usb_isochronous.c | 142 | ||||
-rw-r--r-- | chip/stm32/usb_isochronous.h | 143 |
3 files changed, 286 insertions, 0 deletions
diff --git a/chip/stm32/build.mk b/chip/stm32/build.mk index ff069c9fa6..6e0c190667 100644 --- a/chip/stm32/build.mk +++ b/chip/stm32/build.mk @@ -80,6 +80,7 @@ chip-$(CONFIG_USB_GPIO)+=usb_gpio.o chip-$(CONFIG_USB_HID)+=usb_hid.o chip-$(CONFIG_USB_HID_KEYBOARD)+=usb_hid_keyboard.o chip-$(CONFIG_USB_HID_TOUCHPAD)+=usb_hid_touchpad.o +chip-$(CONFIG_USB_ISOCHRONOUS)+=usb_isochronous.o chip-$(CONFIG_USB_PD_TCPC)+=usb_pd_phy.o chip-$(CONFIG_USB_SPI)+=usb_spi.o endif diff --git a/chip/stm32/usb_isochronous.c b/chip/stm32/usb_isochronous.c new file mode 100644 index 0000000000..fe8402473c --- /dev/null +++ b/chip/stm32/usb_isochronous.c @@ -0,0 +1,142 @@ +/* Copyright 2017 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "stddef.h" +#include "common.h" +#include "config.h" +#include "link_defs.h" +#include "registers.h" +#include "util.h" +#include "usb_api.h" +#include "usb_hw.h" +#include "usb_isochronous.h" + + +/* Console output macro */ +#define CPRINTF(format, args...) cprintf(CC_USB, format, ## args) +#define CPRINTS(format, args...) cprints(CC_USB, format, ## args) + + +/* + * Currently, we only support TX direction for USB isochronous transfer. + * + * According to RM0091, isochronous transfer is always double buffered. + * Addresses of buffers are pointed by `btable_ep[<endpoint>].tx_addr` and + * `btable_ep[<endpoint>].rx_addr`. + * + * DTOG | USB Buffer | App Buffer + * -----+------------+----------- + * 0 | tx_addr | rx_addr + * 1 | rx_addr | tx_addr + * + * That is, when DTOG bit is 0 (see `get_tx_dtog()`), USB hardware will read + * from `tx_addr`, and our application can write new data to `rx_addr` at the + * same time. + * + * Number of bytes in each buffer shall be tracked by `tx_count` and `rx_count` + * respectively. + * + * `get_app_addr()`, `set_app_addr()`, `set_app_count()` help you to to select + * the correct variable to use by given DTOG value, which is available by + * `get_tx_dtog()`. + */ + +static int get_tx_dtog(struct usb_isochronous_config const *config) +{ + return !!(STM32_USB_EP(config->endpoint) & EP_TX_DTOG); +} + +/* + * Gets buffer address that can be used by software (application). + * + * The mapping between application buffer address and current TX DTOG value is + * shown in table above. + */ +static usb_uint *get_app_addr(struct usb_isochronous_config const *config, + int dtog_value) +{ + return config->tx_ram[dtog_value]; +} + +/* + * Sets number of bytes written to application buffer. + */ +static void set_app_count(struct usb_isochronous_config const *config, + int dtog_value, + usb_uint count) +{ + if (dtog_value) + btable_ep[config->endpoint].tx_count = count; + else + btable_ep[config->endpoint].rx_count = count; +} + +void usb_isochronous_init(struct usb_isochronous_config const *config) +{ + int ep = config->endpoint; + + btable_ep[ep].tx_addr = usb_sram_addr(get_app_addr(config, 1)); + btable_ep[ep].rx_addr = usb_sram_addr(get_app_addr(config, 0)); + set_app_count(config, 0, 0); + set_app_count(config, 1, 0); + + STM32_USB_EP(ep) = ((ep << 0) | /* Endpoint Addr */ + EP_TX_VALID | /* start transmit */ + (2 << 9) | /* ISO EP */ + EP_RX_DISAB); +} + +void usb_isochronous_event(struct usb_isochronous_config const *config, + enum usb_ep_event evt) +{ + if (evt == USB_EVENT_RESET) + usb_isochronous_init(config); +} + +void usb_isochronous_tx(struct usb_isochronous_config const *config) +{ + /* + * Clear CTR_TX, note that EP_TX_VALID will *NOT* be cleared by + * hardware, so we don't need to toggle it. + */ + STM32_TOGGLE_EP(config->endpoint, 0, 0, 0); + /* + * Clear buffer count for buffer we just transmitted, so we do not + * transmit the data twice. + */ + set_app_count(config, get_tx_dtog(config), 0); + + hook_call_deferred(config->deferred, 0); +} + +void usb_isochronous_deferred(struct usb_isochronous_config const *config) +{ + const int dtog_value = get_tx_dtog(config); + usb_uint *app_addr = get_app_addr(config, dtog_value); + size_t count = config->tx_callback(app_addr, config->tx_size); + + set_app_count(config, dtog_value, count); +} + +int usb_isochronous_iface_handler(struct usb_isochronous_config const *config, + usb_uint *ep0_buf_rx, + usb_uint *ep0_buf_tx) +{ + int ret = -1; + + if (ep0_buf_rx[0] == (USB_DIR_OUT | + USB_TYPE_STANDARD | + USB_RECIP_INTERFACE | + USB_REQ_SET_INTERFACE << 8)) { + ret = config->set_interface(ep0_buf_rx[1], ep0_buf_rx[2]); + + if (ret == 0) { + /* ACK */ + btable_ep[0].tx_count = 0; + STM32_TOGGLE_EP(0, EP_TX_RX_MASK, EP_TX_RX_VALID, 0); + } + } + return ret; +} diff --git a/chip/stm32/usb_isochronous.h b/chip/stm32/usb_isochronous.h new file mode 100644 index 0000000000..423ce339ce --- /dev/null +++ b/chip/stm32/usb_isochronous.h @@ -0,0 +1,143 @@ +/* Copyright 2017 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef __CROS_EC_USB_ISOCHRONOUS_H +#define __CROS_EC_USB_ISOCHRONOUS_H + +#include "common.h" +#include "compile_time_macros.h" +#include "hooks.h" +#include "usb_descriptor.h" +#include "usb_hw.h" + +/* Currently, we only support TX direction for USB isochronous transfer. */ + +struct usb_isochronous_config { + int endpoint; + + /* + * Deferred function to call to handle USB request. + */ + const struct deferred_data *deferred; + + /* + * On TX complete, this function will be called to ask for more data to + * transmit. + * + * @param usb_addr USB buffer, an uint8_t pointer that can be + * passed to memcpy_to_usbram() + * @param tx_size config->tx_size + * @return size_t Number of bytes written to USB buffer + */ + size_t (*tx_callback)(usb_uint *usb_addr, size_t tx_size); + + /* + * Received SET_INTERFACE request. + * + * @param alternate_setting new bAlternateSetting value. + * @param interface interface number. + * @return int 0 for success, -1 for unknown setting. + */ + int (*set_interface)(usb_uint alternate_setting, usb_uint interface); + + /* USB packet RAM buffer size. */ + size_t tx_size; + /* USB packet RAM buffers. */ + usb_uint *tx_ram[2]; +}; + +/* Define an USB isochronous interface */ +#define USB_ISOCHRONOUS_CONFIG_FULL(NAME, \ + INTERFACE, \ + INTERFACE_CLASS, \ + INTERFACE_SUBCLASS, \ + INTERFACE_PROTOCOL, \ + INTERFACE_NAME, \ + ENDPOINT, \ + TX_SIZE, \ + TX_CALLBACK, \ + SET_INTERFACE) \ + BUILD_ASSERT(TX_SIZE > 0); \ + BUILD_ASSERT((TX_SIZE < 64 && (TX_SIZE & 0x01) == 0) || \ + (TX_SIZE < 1024 && (TX_SIZE & 0x1f) == 0)); \ + /* Declare buffer */ \ + static usb_uint CONCAT2(NAME, _ep_tx_buffer_0)[TX_SIZE / 2] __usb_ram; \ + static usb_uint CONCAT2(NAME, _ep_tx_buffer_1)[TX_SIZE / 2] __usb_ram; \ + static void CONCAT2(NAME, _deferred_)(void); \ + DECLARE_DEFERRED(CONCAT2(NAME, _deferred_)); \ + struct usb_isochronous_config const NAME = { \ + .endpoint = ENDPOINT, \ + .deferred = &CONCAT2(NAME, _deferred__data), \ + .tx_callback = TX_CALLBACK, \ + .set_interface = SET_INTERFACE, \ + .tx_size = TX_SIZE, \ + .tx_ram = { \ + CONCAT2(NAME, _ep_tx_buffer_0), \ + CONCAT2(NAME, _ep_tx_buffer_1), \ + }, \ + }; \ + const struct usb_interface_descriptor \ + USB_IFACE_DESC(INTERFACE) = { \ + .bLength = USB_DT_INTERFACE_SIZE, \ + .bDescriptorType = USB_DT_INTERFACE, \ + .bInterfaceNumber = INTERFACE, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 0, \ + .bInterfaceClass = INTERFACE_CLASS, \ + .bInterfaceSubClass = INTERFACE_SUBCLASS, \ + .bInterfaceProtocol = INTERFACE_PROTOCOL, \ + .iInterface = INTERFACE_NAME, \ + }; \ + const struct usb_interface_descriptor \ + USB_CONF_DESC(CONCAT3(iface, INTERFACE, _1iface)) = { \ + .bLength = USB_DT_INTERFACE_SIZE, \ + .bDescriptorType = USB_DT_INTERFACE, \ + .bInterfaceNumber = INTERFACE, \ + .bAlternateSetting = 1, \ + .bNumEndpoints = 1, \ + .bInterfaceClass = INTERFACE_CLASS, \ + .bInterfaceSubClass = INTERFACE_SUBCLASS, \ + .bInterfaceProtocol = INTERFACE_PROTOCOL, \ + .iInterface = INTERFACE_NAME, \ + }; \ + const struct usb_endpoint_descriptor \ + USB_EP_DESC(INTERFACE, 0) = { \ + .bLength = USB_DT_ENDPOINT_SIZE, \ + .bDescriptorType = USB_DT_ENDPOINT, \ + .bEndpointAddress = 0x80 | ENDPOINT, \ + .bmAttributes = 0x01 /* Isochronous IN */, \ + .wMaxPacketSize = TX_SIZE, \ + .bInterval = 1, \ + }; \ + static void CONCAT2(NAME, _ep_tx)(void) \ + { \ + usb_isochronous_tx(&NAME); \ + } \ + static void CONCAT2(NAME, _ep_event)(enum usb_ep_event evt) \ + { \ + usb_isochronous_event(&NAME, evt); \ + } \ + static int CONCAT2(NAME, _handler)(usb_uint *rx, usb_uint *tx) \ + { \ + return usb_isochronous_iface_handler(&NAME, rx, tx); \ + } \ + USB_DECLARE_IFACE(INTERFACE, CONCAT2(NAME, _handler)); \ + USB_DECLARE_EP(ENDPOINT, \ + CONCAT2(NAME, _ep_tx), \ + CONCAT2(NAME, _ep_tx), \ + CONCAT2(NAME, _ep_event)); \ + static void CONCAT2(NAME, _deferred_)(void) \ + { \ + usb_isochronous_deferred(&NAME); \ + } + +void usb_isochronous_deferred(struct usb_isochronous_config const *config); +void usb_isochronous_tx(struct usb_isochronous_config const *config); +void usb_isochronous_event(struct usb_isochronous_config const *config, + enum usb_ep_event event); +int usb_isochronous_iface_handler(struct usb_isochronous_config const *config, + usb_uint *ep0_buf_rx, + usb_uint *ep0_buf_tx); +#endif /* __CROS_EC_USB_ISOCHRONOUS_H */ |