summaryrefslogtreecommitdiff
path: root/chip
diff options
context:
space:
mode:
authorWei-Han Chen <stimim@google.com>2017-12-11 14:41:46 +0800
committerchrome-bot <chrome-bot@chromium.org>2018-01-31 08:29:46 -0800
commitb245e71e82d4da9b1be27c6de45da1d2bd460751 (patch)
tree61bc0d5203c1aa46b87e97f63db2505a0e9fc8f1 /chip
parent924d21d904b9f2c640ee5b0ccabcf78200456a0f (diff)
downloadchrome-ec-b245e71e82d4da9b1be27c6de45da1d2bd460751.tar.gz
stm32: add usb_isochronous
Templates for USB isochronous implementation. Current implementation only supports TX transmit. Example of usage can be found in CL:803414. Basically, declare an USB isochronous interface by USB_ISOCHRONOUS_CONFIG_FULL(<NAME>, <INTERFACE_NUM>, <USB_CLASS>, <USB_SUBCLASS>, <SUB_PROTOCOL>, <USB_STR_FOR_INTERFACE_NAME>, <USB_EP_NUM>, <PACKET_SIZE>, <TX_CALLBACK>, <SET_INTERFACE>) where <PACKET_SIZE> is size of each USB packet, <TX_CALLBACK> is called when USB hardware has completed a packet. The buffer that USB is not currently using will be passed to <TX_CALLBACK>, allow applications to write next packet to it. When a SET_INTERFACE packet is received, <SET_INTERFACE> will be called with bAlternateSetting and bInterfaceNumber. We will declare interface descriptor with bAlternateSetting = 0 and 1 for you, if you need more alternate settings, you need to declare by yourself. BUG=b:70482333 TEST=manually on reworked staff board Signed-off-by: Wei-Han Chen <stimim@google.com> Change-Id: Ic6d41da6ddd7945edf0bdfff55ede38a97661783 Reviewed-on: https://chromium-review.googlesource.com/818853 Commit-Ready: Wei-Han Chen <stimim@chromium.org> Tested-by: Wei-Han Chen <stimim@chromium.org> Reviewed-by: Wei-Han Chen <stimim@chromium.org> Reviewed-by: Nicolas Boichat <drinkcat@chromium.org>
Diffstat (limited to 'chip')
-rw-r--r--chip/stm32/build.mk1
-rw-r--r--chip/stm32/usb_isochronous.c142
-rw-r--r--chip/stm32/usb_isochronous.h143
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 */