| /* 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" |
| |
| struct usb_isochronous_config; |
| |
| /* |
| * Currently, we only support TX direction for USB isochronous transfer. |
| */ |
| |
| /* |
| * Copy `n` bytes from `src` to USB buffer. |
| * |
| * We are using double buffering, therefore, we need to write to the buffer that |
| * hardware is not currently using. This function will handle this for you. |
| * |
| * Sample usage: |
| * |
| * int buffer_id = -1; // initialize to unknown |
| * int ret; |
| * size_t dst_offset = 0, src_offset = 0; |
| * const uint8_t* buf; |
| * size_t buf_size; |
| * |
| * while (1) { |
| * buf = ...; |
| * buf_size = ...; |
| * if (no more data) { |
| * buf = NULL; |
| * break; |
| * } else { |
| * ret = usb_isochronous_write_buffer( |
| * config, buf, buf_size, dst_offset, |
| * &buffer_id, |
| * 0); |
| * if (ret < 0) |
| * goto FAILED; |
| * dst_offset += ret; |
| * if (ret != buf_size) { |
| * // no more space in TX buffer |
| * src_offset = ret; |
| * break; |
| * } |
| * } |
| * } |
| * // commit |
| * ret = usb_isochronous_write_buffer( |
| * config, NULL, 0, dst_offset, |
| * &buffer_id, 1); |
| * if (ret < 0) |
| * goto FAILED; |
| * if (buf) |
| * // buf[src_offset ... buf_size] haven't been sent yet, send them |
| * // later. |
| * |
| * On the first invocation, on success, `ret` will be number of bytes that have |
| * been written, and `buffer_id` will be 0 or 1, depending on which buffer we |
| * are writing. And commit=0 means there are pending data, so buffer count |
| * won't be set yet. |
| * |
| * On the second invocation, since buffer_id is not -1, we will return an error |
| * if hardware has switched to this buffer (it means we spent too much time |
| * filling buffer). And commit=1 means we are done, and buffer count will be |
| * set to `dst_offset + num_bytes_written` on success. |
| * |
| * @return -EC_ERROR_CODE on failure, or number of bytes written on success. |
| */ |
| int usb_isochronous_write_buffer( |
| struct usb_isochronous_config const *config, |
| const uint8_t *src, |
| size_t n, |
| size_t dst_offset, |
| int *buffer_id, |
| int commit); |
| |
| struct usb_isochronous_config { |
| int endpoint; |
| |
| /* |
| * On TX complete, this function will be called in **interrupt |
| * context**. |
| * |
| * @param config the usb_isochronous_config of the USB interface. |
| */ |
| void (*tx_callback)(struct usb_isochronous_config const *config); |
| |
| /* |
| * 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, \ |
| NUM_EXTRA_ENDPOINTS) \ |
| 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; \ |
| struct usb_isochronous_config const NAME = { \ |
| .endpoint = ENDPOINT, \ |
| .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 + NUM_EXTRA_ENDPOINTS, \ |
| .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)); \ |
| |
| 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 */ |