diff options
Diffstat (limited to 'chip')
-rw-r--r-- | chip/stm32/build.mk | 1 | ||||
-rw-r--r-- | chip/stm32/usb_i2c.c | 140 | ||||
-rw-r--r-- | chip/stm32/usb_i2c.h | 182 |
3 files changed, 323 insertions, 0 deletions
diff --git a/chip/stm32/build.mk b/chip/stm32/build.mk index 6878c76065..f7af790555 100644 --- a/chip/stm32/build.mk +++ b/chip/stm32/build.mk @@ -59,3 +59,4 @@ chip-$(CONFIG_USB_GPIO)+=usb_gpio.o chip-$(CONFIG_USB_HID)+=usb_hid.o chip-$(CONFIG_USB_PD_TCPC)+=usb_pd_phy.o chip-$(CONFIG_USB_SPI)+=usb_spi.o +chip-$(CONFIG_USB_I2C)+=usb_i2c.o diff --git a/chip/stm32/usb_i2c.c b/chip/stm32/usb_i2c.c new file mode 100644 index 0000000000..1cc13bc304 --- /dev/null +++ b/chip/stm32/usb_i2c.c @@ -0,0 +1,140 @@ +/* Copyright 2016 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 "common.h" +#include "link_defs.h" +#include "registers.h" +#include "i2c.h" +#include "usb_descriptor.h" +#include "usb_i2c.h" +#include "util.h" + +#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args) + + +static int16_t usb_i2c_map_error(int error) +{ + switch (error) { + case EC_SUCCESS: return USB_I2C_SUCCESS; + case EC_ERROR_TIMEOUT: return USB_I2C_TIMEOUT; + case EC_ERROR_BUSY: return USB_I2C_BUSY; + default: return USB_I2C_UNKNOWN_ERROR | (error & 0x7fff); + } +} + +static uint8_t usb_i2c_read_packet(struct usb_i2c_config const *config) +{ + size_t i; + uint16_t bytes = btable_ep[config->endpoint].rx_count & 0x3ff; + size_t count = MAX((bytes + 1) / 2, USB_MAX_PACKET_SIZE / 2); + + /* + * The USB peripheral doesn't support DMA access to its packet + * RAM so we have to copy messages out into a bounce buffer. + */ + for (i = 0; i < count; ++i) + config->buffer[i] = config->rx_ram[i]; + + /* + * RX packet consumed, mark the packet as VALID. The master + * could queue up the next command while we process this I2C + * transaction and prepare the response. + */ + STM32_TOGGLE_EP(config->endpoint, EP_RX_MASK, EP_RX_VALID, 0); + + return bytes; +} + +static void usb_i2c_write_packet(struct usb_i2c_config const *config, + uint8_t count) +{ + size_t i; + /* count is bounds checked in usb_i2c_deferred. */ + + /* + * Copy read bytes and status back out of bounce buffer and + * update TX packet state (mark as VALID for master to read). + */ + for (i = 0; i < (count + 1) / 2; ++i) + config->tx_ram[i] = config->buffer[i]; + + btable_ep[config->endpoint].tx_count = count; + + STM32_TOGGLE_EP(config->endpoint, EP_TX_MASK, EP_TX_VALID, 0); +} + +static int rx_valid(struct usb_i2c_config const *config) +{ + return (STM32_USB_EP(config->endpoint) & EP_RX_MASK) == EP_RX_VALID; +} + +void usb_i2c_deferred(struct usb_i2c_config const *config) +{ + /* + * And if there is a USB packet waiting we process it and generate a + * response. + */ + if (!rx_valid(config)) { + uint16_t count = usb_i2c_read_packet(config); + int portindex = (config->buffer[0] >> 0) & 0xff; + + /* Convert 7-bit slave address to stm32 8-bit address. */ + uint8_t slave_addr = (config->buffer[0] >> 7) & 0xfe; + int write_count = (config->buffer[1] >> 0) & 0xff; + int read_count = (config->buffer[1] >> 8) & 0xff; + int port = 0; + + config->buffer[0] = 0; + config->buffer[1] = 0; + + if (write_count > USB_I2C_MAX_WRITE_COUNT || + write_count != (count - 4)) { + config->buffer[0] = USB_I2C_WRITE_COUNT_INVALID; + } else if (read_count > USB_I2C_MAX_READ_COUNT) { + config->buffer[0] = USB_I2C_READ_COUNT_INVALID; + } else if (portindex >= i2c_ports_used) { + config->buffer[0] = USB_I2C_PORT_INVALID; + } else { + port = i2c_ports[portindex].port; + config->buffer[0] = usb_i2c_map_error( + i2c_xfer(port, slave_addr, + (uint8_t *)(config->buffer + 2), + write_count, + (uint8_t *)(config->buffer + 2), + read_count, I2C_XFER_SINGLE)); + } + + usb_i2c_write_packet(config, read_count + 4); + } +} + +void usb_i2c_tx(struct usb_i2c_config const *config) +{ + STM32_TOGGLE_EP(config->endpoint, EP_TX_MASK, EP_TX_NAK, 0); +} + +void usb_i2c_rx(struct usb_i2c_config const *config) +{ + STM32_TOGGLE_EP(config->endpoint, EP_RX_MASK, EP_RX_NAK, 0); + + hook_call_deferred(config->deferred, 0); +} + +void usb_i2c_reset(struct usb_i2c_config const *config) +{ + int endpoint = config->endpoint; + + btable_ep[endpoint].tx_addr = usb_sram_addr(config->tx_ram); + btable_ep[endpoint].tx_count = 0; + + btable_ep[endpoint].rx_addr = usb_sram_addr(config->rx_ram); + btable_ep[endpoint].rx_count = + 0x8000 | ((USB_MAX_PACKET_SIZE / 32 - 1) << 10); + + STM32_USB_EP(endpoint) = ((endpoint << 0) | /* Endpoint Addr*/ + (2 << 4) | /* TX NAK */ + (0 << 9) | /* Bulk EP */ + (3 << 12)); /* RX Valid */ +} diff --git a/chip/stm32/usb_i2c.h b/chip/stm32/usb_i2c.h new file mode 100644 index 0000000000..f9b3d54faa --- /dev/null +++ b/chip/stm32/usb_i2c.h @@ -0,0 +1,182 @@ +/* Copyright 2016 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_I2C_H +#define __CROS_EC_USB_I2C_H + +/* STM32 USB I2C driver for Chrome EC */ + +#include "compile_time_macros.h" +#include "hooks.h" +#include "usb_descriptor.h" + +/* + * Command: + * +----------+-----------+---------------+---------------+---------------+ + * | port: 1B | addr: 1B | wr count : 1B | rd count : 1B | data : <= 60B | + * +----------+-----------+---------------+---------------+---------------+ + * + * port address: 1 byte, stm32 i2c interface index + * + * slave address: 1 byte, i2c 7-bit bus address + * + * write count: 1 byte, zero based count of bytes to write + * + * read count: 1 byte, zero based count of bytes to read + * + * data: write payload up to 60 bytes of data to write, + * length must match write count + * + * Response: + * +-------------+---+---+-----------------------+ + * | status : 2B | 0 | 0 | read payload : <= 60B | + * +-------------+---+---+-----------------------+ + * + * status: 2 byte status + * 0x0000: Success + * 0x0001: I2C timeout + * 0x0002: Busy, try again + * This can happen if someone else has acquired the shared memory + * buffer that the I2C driver uses as /dev/null + * 0x0003: Write count invalid (> 60 bytes, or mismatch with payload) + * 0x0004: Read count invalid (> 60 bytes) + * 0x0005: The port specified is invalid. + * 0x8000: Unknown error mask + * The bottom 15 bits will contain the bottom 15 bits from the EC + * error code. + * + * read payload: up to 60 bytes of data read from I2C, length will match + * requested read count + */ + +enum usb_i2c_error { + USB_I2C_SUCCESS = 0x0000, + USB_I2C_TIMEOUT = 0x0001, + USB_I2C_BUSY = 0x0002, + USB_I2C_WRITE_COUNT_INVALID = 0x0003, + USB_I2C_READ_COUNT_INVALID = 0x0004, + USB_I2C_PORT_INVALID = 0x0005, + USB_I2C_UNKNOWN_ERROR = 0x8000, +}; + + +#define USB_I2C_MAX_WRITE_COUNT 60 +#define USB_I2C_MAX_READ_COUNT 60 + +BUILD_ASSERT(USB_MAX_PACKET_SIZE == (1 + 1 + 1 + 1 + USB_I2C_MAX_WRITE_COUNT)); +BUILD_ASSERT(USB_MAX_PACKET_SIZE == (2 + 1 + 1 + USB_I2C_MAX_READ_COUNT)); + +/* + * Compile time Per-USB gpio configuration stored in flash. Instances of this + * structure are provided by the user of the USB gpio. This structure binds + * together all information required to operate a USB gpio. + */ +struct usb_i2c_config { + /* + * Interface and endpoint indicies. + */ + int interface; + int endpoint; + + /* + * Deferred function to call to handle I2C request. + */ + void (*deferred)(void); + + /* + * Pointers to USB packet RAM and bounce buffer. + */ + uint16_t *buffer; + usb_uint *rx_ram; + usb_uint *tx_ram; +}; + +/* + * Convenience macro for defining a USB I2C bridge driver. + * + * NAME is used to construct the names of the trampoline functions and the + * usb_i2c_config struct, the latter is just called NAME. + * + * INTERFACE is the index of the USB interface to associate with this + * I2C driver. + * + * ENDPOINT is the index of the USB bulk endpoint used for receiving and + * transmitting bytes. + */ +#define USB_I2C_CONFIG(NAME, \ + INTERFACE, \ + ENDPOINT) \ + static uint16_t \ + CONCAT2(NAME, _buffer_)[USB_MAX_PACKET_SIZE / 2]; \ + static usb_uint CONCAT2(NAME, _ep_rx_buffer_) \ + [USB_MAX_PACKET_SIZE / 2] __usb_ram; \ + static usb_uint CONCAT2(NAME, _ep_tx_buffer_) \ + [USB_MAX_PACKET_SIZE / 2] __usb_ram; \ + static void CONCAT2(NAME, _deferred_)(void); \ + struct usb_i2c_config const NAME = { \ + .interface = INTERFACE, \ + .endpoint = ENDPOINT, \ + .deferred = CONCAT2(NAME, _deferred_), \ + .buffer = CONCAT2(NAME, _buffer_), \ + .rx_ram = CONCAT2(NAME, _ep_rx_buffer_), \ + .tx_ram = CONCAT2(NAME, _ep_tx_buffer_), \ + }; \ + const struct usb_interface_descriptor \ + USB_IFACE_DESC(INTERFACE) = { \ + .bLength = USB_DT_INTERFACE_SIZE, \ + .bDescriptorType = USB_DT_INTERFACE, \ + .bInterfaceNumber = INTERFACE, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 2, \ + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \ + .bInterfaceSubClass = USB_SUBCLASS_GOOGLE_I2C, \ + .bInterfaceProtocol = USB_PROTOCOL_GOOGLE_I2C, \ + .iInterface = 0, \ + }; \ + const struct usb_endpoint_descriptor \ + USB_EP_DESC(INTERFACE, 0) = { \ + .bLength = USB_DT_ENDPOINT_SIZE, \ + .bDescriptorType = USB_DT_ENDPOINT, \ + .bEndpointAddress = 0x80 | ENDPOINT, \ + .bmAttributes = 0x02 /* Bulk IN */, \ + .wMaxPacketSize = USB_MAX_PACKET_SIZE, \ + .bInterval = 10, \ + }; \ + const struct usb_endpoint_descriptor \ + USB_EP_DESC(INTERFACE, 1) = { \ + .bLength = USB_DT_ENDPOINT_SIZE, \ + .bDescriptorType = USB_DT_ENDPOINT, \ + .bEndpointAddress = ENDPOINT, \ + .bmAttributes = 0x02 /* Bulk OUT */, \ + .wMaxPacketSize = USB_MAX_PACKET_SIZE, \ + .bInterval = 0, \ + }; \ + static void CONCAT2(NAME, _ep_tx_) (void) \ + { usb_i2c_tx(&NAME); } \ + static void CONCAT2(NAME, _ep_rx_) (void) \ + { usb_i2c_rx(&NAME); } \ + static void CONCAT2(NAME, _ep_reset_)(void) \ + { usb_i2c_reset(&NAME); } \ + USB_DECLARE_EP(ENDPOINT, \ + CONCAT2(NAME, _ep_tx_), \ + CONCAT2(NAME, _ep_rx_), \ + CONCAT2(NAME, _ep_reset_)); \ + static void CONCAT2(NAME, _deferred_)(void) \ + { usb_i2c_deferred(&NAME); } \ + DECLARE_DEFERRED(CONCAT2(NAME, _deferred_)); + +/* + * Handle I2C request in a deferred callback. + */ +void usb_i2c_deferred(struct usb_i2c_config const *config); + +/* + * These functions are used by the trampoline functions defined above to + * connect USB endpoint events with the generic USB GPIO driver. + */ +void usb_i2c_tx(struct usb_i2c_config const *config); +void usb_i2c_rx(struct usb_i2c_config const *config); +void usb_i2c_reset(struct usb_i2c_config const *config); + +#endif /* __CROS_EC_USB_I2C_H */ |