/* Copyright 2014 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "common.h" #include "gpio.h" #include "link_defs.h" #include "registers.h" #include "spi.h" #include "timer.h" #include "usb_descriptor.h" #include "usb_hw.h" #include "usb_spi.h" #include "util.h" /* How long to wait for a flash page programming. */ #define FLASH_BUSY_POLL_TIMEOUT_USEC (1000 * MSEC) const uint8_t JEDEC_READ_STATUS = 0x05; const uint8_t JEDEC_STATUS_BUSY = 0x01; /* Forward declare platform specific functions. */ static bool usb_spi_received_packet(void); static bool usb_spi_transmitted_packet(void); static void usb_spi_read_packet(struct usb_spi_packet_ctx *packet); static void usb_spi_write_packet(struct usb_spi_packet_ctx *packet); struct usb_spi_transfer_ctx { /* Address of transfer buffer. */ uint8_t *buffer; /* Number of bytes in the transfer. */ size_t transfer_size; /* Number of bytes transferred. */ size_t transfer_index; }; enum usb_spi_mode { /* No tasks are required. */ USB_SPI_MODE_IDLE = 0, /* Indicates the device needs to send it's USB SPI configuration.*/ USB_SPI_MODE_SEND_CONFIGURATION, /* Indicates the device needs to respond to chip select. */ USB_SPI_MODE_SEND_CHIP_SELECT_RESPONSE, /* Indicates we device needs start the SPI transfer. */ USB_SPI_MODE_START_SPI, /* Indicates we should start a transfer response. */ USB_SPI_MODE_START_RESPONSE, /* Indicates we need to continue a transfer response. */ USB_SPI_MODE_CONTINUE_RESPONSE, }; struct usb_spi_state { /* * The SPI bridge must be enabled both locally and by the host to allow * access to the SPI device. The enabled_host flag is set and cleared * by sending USB_SPI_REQ_ENABLE and USB_SPI_REQ_DISABLE to the device * control endpoint. The enabled_device flag is set by calling * usb_spi_enable. */ uint8_t enabled_host; uint8_t enabled_device; /* * The current enabled state. This is only updated in the deferred * callback. Whenever either of the host or device specific enable * flags is changed the deferred callback is queued, and it will check * their combined state against this flag. If the combined state is * different, then one of usb_spi_board_enable or usb_spi_board_disable * is called and this flag is updated. This ensures that the board * specific state update routines are only called from the deferred * callback. */ uint8_t enabled; /* * The index of the SPI port currently receiving forwarded transactions, * default is zero. */ uint8_t current_spi_device_idx; /* Mark the current operating mode. */ enum usb_spi_mode mode; /* * Stores the status code response for the transfer, delivered in the * header for the first response packet. Error code is cleared during * first RX packet and set if a failure occurs. */ uint16_t status_code; /* Stores the content from the USB packets */ struct usb_spi_packet_ctx receive_packet; struct usb_spi_packet_ctx transmit_packet; /* * Flags describing if and how multi-lane (dual/quad), double transfer * rate, and other advanced flash protocol features are to be used. */ uint32_t flash_flags; /* * Context structures representing the progress receiving the SPI * write data and transmitting the SPI read data. */ struct usb_spi_transfer_ctx spi_write_ctx; struct usb_spi_transfer_ctx spi_read_ctx; }; /* * 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_spi_config { /* Interface and endpoint indices. */ int interface; int endpoint; /* Deferred function to call to handle SPI request. */ const struct deferred_data *deferred; /* Pointers to USB endpoint buffers. */ usb_uint *ep_rx_ram; usb_uint *ep_tx_ram; }; /* * Handle SPI request in a deferred callback. */ void usb_spi_deferred(void); /* * Storage of configuration and state of USB->SPI bridge. */ static uint16_t usb_spi_buffer_[(USB_SPI_BUFFER_SIZE + 1) / 2]; static usb_uint usb_spi_ep_rx_buffer_[USB_MAX_PACKET_SIZE / 2] __usb_ram; static usb_uint usb_spi_ep_tx_buffer_[USB_MAX_PACKET_SIZE / 2] __usb_ram; static struct usb_spi_state usb_spi_state = { .enabled_host = 0, .enabled_device = 0, .enabled = 0, .current_spi_device_idx = 0, .spi_write_ctx.buffer = (uint8_t *)usb_spi_buffer_, .spi_read_ctx.buffer = (uint8_t *)usb_spi_buffer_, }; DECLARE_DEFERRED(usb_spi_deferred); struct usb_spi_config const usb_spi = { .interface = USB_IFACE_SPI, .endpoint = USB_EP_SPI, .deferred = &usb_spi_deferred_data, .ep_rx_ram = usb_spi_ep_rx_buffer_, .ep_tx_ram = usb_spi_ep_tx_buffer_, }; /* * Map EC error codes to USB_SPI error codes. * * @param error EC error code * * @returns USB SPI error code based on the mapping. */ static int16_t usb_spi_map_error(int error) { switch (error) { case EC_SUCCESS: return USB_SPI_SUCCESS; case EC_ERROR_TIMEOUT: return USB_SPI_TIMEOUT; case EC_ERROR_BUSY: return USB_SPI_BUSY; default: return USB_SPI_UNKNOWN_ERROR | (error & 0x7fff); } } /* * Read data into the receive buffer. * * @param dst Destination receive context we are writing data to. * @param src Source packet context we are reading data from. * * @returns USB_SPI_RX_DATA_OVERFLOW if the source packet is too large */ static int usb_spi_read_usb_packet(struct usb_spi_transfer_ctx *dst, const struct usb_spi_packet_ctx *src) { size_t max_read_length = dst->transfer_size - dst->transfer_index; size_t bytes_in_buffer = src->packet_size - src->header_size; const uint8_t *packet_buffer = src->bytes + src->header_size; if (bytes_in_buffer > max_read_length) { /* * An error occurred, we should not receive more data than * the buffer can support. */ return USB_SPI_RX_DATA_OVERFLOW; } memcpy(dst->buffer + dst->transfer_index, packet_buffer, bytes_in_buffer); dst->transfer_index += bytes_in_buffer; return USB_SPI_SUCCESS; } /* * Fill the USB packet with data from the transmit buffer. * * @param dst Destination packet context we are writing data to. * @param src Source transmit context we are reading data from. */ static void usb_spi_fill_usb_packet(struct usb_spi_packet_ctx *dst, struct usb_spi_transfer_ctx *src) { size_t transfer_size = src->transfer_size - src->transfer_index; size_t max_buffer_size = USB_MAX_PACKET_SIZE - dst->header_size; uint8_t *packet_buffer = dst->bytes + dst->header_size; if (transfer_size > max_buffer_size) transfer_size = max_buffer_size; memcpy(packet_buffer, src->buffer + src->transfer_index, transfer_size); dst->packet_size = dst->header_size + transfer_size; src->transfer_index += transfer_size; } /* * Setup the USB SPI state to start a new SPI transfer. * * @param config USB SPI config * @param write_count Number of bytes to write in the SPI transfer * @param read_count Number of bytes to read in the SPI transfer */ static void usb_spi_setup_transfer(size_t write_count, size_t read_count) { /* Reset any status code. */ usb_spi_state.status_code = USB_SPI_SUCCESS; /* Reset the write and read counts. */ usb_spi_state.spi_write_ctx.transfer_size = write_count; usb_spi_state.spi_write_ctx.transfer_index = 0; usb_spi_state.spi_read_ctx.transfer_size = read_count; usb_spi_state.spi_read_ctx.transfer_index = 0; } /* * Handle USB events that will reset the USB SPI state. * * @param config USB SPI config */ static void usb_spi_reset_interface(void) { /* Setup a 0 byte transfer to clear the contexts. */ usb_spi_setup_transfer(0, 0); } /* * Returns if the response transfer is in progress. * * @param config USB SPI config * * @returns True if a response transfer is in progress. */ static bool usb_spi_response_in_progress(void) { if ((usb_spi_state.mode == USB_SPI_MODE_START_RESPONSE) || (usb_spi_state.mode == USB_SPI_MODE_CONTINUE_RESPONSE)) { return true; } return false; } /* * Prep the state to construct a new response. This sets the transfer * contexts, the mode, and status code. If a non-zero status code is * returned, then no payload will be transmitted. * * @param config USB SPI config * @param status_code status code to set for the response. */ static void setup_transfer_response(uint16_t status_code) { usb_spi_state.status_code = status_code; usb_spi_state.spi_read_ctx.transfer_index = 0; usb_spi_state.mode = USB_SPI_MODE_START_RESPONSE; /* If an error occurred, transmit an empty start packet. */ if (status_code != USB_SPI_SUCCESS) usb_spi_state.spi_read_ctx.transfer_size = 0; } /* * Constructs the response packet containing the SPI configuration. * * @param config USB SPI config * @param packet Packet buffer we will be transmitting. */ static void create_spi_config_response(struct usb_spi_packet_ctx *packet) { const struct spi_device_t *current_device = &spi_devices[usb_spi_state.current_spi_device_idx]; /* Construct the response packet. */ packet->rsp_config.packet_id = USB_SPI_PKT_ID_RSP_USB_SPI_CONFIG; packet->rsp_config.max_write_count = USB_SPI_MAX_WRITE_COUNT; packet->rsp_config.max_read_count = USB_SPI_MAX_READ_COUNT; /* Set the feature flags. */ packet->rsp_config.feature_bitmap = 0; #ifndef CONFIG_SPI_HALFDUPLEX packet->rsp_config.feature_bitmap |= USB_SPI_FEATURE_FULL_DUPLEX_SUPPORTED; #endif #ifdef CONFIG_USB_SPI_FLASH_EXTENSIONS packet->rsp_config.feature_bitmap |= USB_SPI_FEATURE_FLASH_EXTENSIONS; if (current_device->usb_flags & USB_SPI_FLASH_DUAL_SUPPORT) packet->rsp_config.feature_bitmap |= USB_SPI_FEATURE_DUAL_MODE_SUPPORTED; if (current_device->usb_flags & USB_SPI_FLASH_QUAD_SUPPORT) packet->rsp_config.feature_bitmap |= USB_SPI_FEATURE_QUAD_MODE_SUPPORTED; if (current_device->usb_flags & USB_SPI_FLASH_OCTO_SUPPORT) packet->rsp_config.feature_bitmap |= USB_SPI_FEATURE_OCTO_MODE_SUPPORTED; if (current_device->usb_flags & USB_SPI_FLASH_DTR_SUPPORT) packet->rsp_config.feature_bitmap |= USB_SPI_FEATURE_DTR_SUPPORTED; #else (void)current_device; /* Avoid warning about unused variable. */ #endif packet->packet_size = sizeof(struct usb_spi_response_configuration_v2); } static void create_spi_chip_select_response(struct usb_spi_packet_ctx *packet) { /* Construct the response packet. */ packet->rsp_cs.packet_id = USB_SPI_PKT_ID_RSP_CHIP_SELECT; packet->rsp_cs.status_code = 0; packet->packet_size = sizeof(packet->rsp_cs); } /* * If we have a transfer response in progress, this will construct the * next entry. If no transfer is in progress or if we are unable to * create the next packet, it will not modify tx_packet. * * @param config USB SPI config * @param packet Packet buffer we will be transmitting. */ static void usb_spi_create_spi_transfer_response(struct usb_spi_packet_ctx *transmit_packet) { if (!usb_spi_response_in_progress()) return; if (usb_spi_state.spi_read_ctx.transfer_index == 0) { /* Transmit the first packet with the status code. */ transmit_packet->header_size = offsetof(struct usb_spi_response_v2, data); transmit_packet->rsp_start.packet_id = USB_SPI_PKT_ID_RSP_TRANSFER_START; transmit_packet->rsp_start.status_code = usb_spi_state.status_code; usb_spi_fill_usb_packet(transmit_packet, &usb_spi_state.spi_read_ctx); } else if (usb_spi_state.spi_read_ctx.transfer_index < usb_spi_state.spi_read_ctx.transfer_size) { /* Transmit the continue packets. */ transmit_packet->header_size = offsetof(struct usb_spi_continue_v2, data); transmit_packet->rsp_continue.packet_id = USB_SPI_PKT_ID_RSP_TRANSFER_CONTINUE; transmit_packet->rsp_continue.data_index = usb_spi_state.spi_read_ctx.transfer_index; usb_spi_fill_usb_packet(transmit_packet, &usb_spi_state.spi_read_ctx); } if (usb_spi_state.spi_read_ctx.transfer_index < usb_spi_state.spi_read_ctx.transfer_size) { usb_spi_state.mode = USB_SPI_MODE_CONTINUE_RESPONSE; } else { usb_spi_state.mode = USB_SPI_MODE_IDLE; } } #ifdef CONFIG_USB_SPI_FLASH_EXTENSIONS /* * Decodes the header fields of a Flash Command Start Packet, and sets up the * transaction depending on if it is read or write. */ static void setup_flash_transfer(struct usb_spi_packet_ctx *packet) { const uint32_t flags = packet->cmd_flash_start.flags; usb_spi_state.flash_flags = flags; const uint8_t opcode_count = (flags & FLASH_FLAG_OPCODE_LEN_MSK) >> FLASH_FLAG_OPCODE_LEN_POS; const uint8_t addr_count = (flags & FLASH_FLAG_ADDR_LEN_MSK) >> FLASH_FLAG_ADDR_LEN_POS; const bool write_enable = !!(flags & FLASH_FLAG_WRITE_ENABLE); if ((packet->cmd_flash_start.flags & FLASH_FLAG_READ_WRITE_MSK) == FLASH_FLAG_READ_WRITE_WRITE) { size_t write_count = packet->cmd_flash_start.count; if (write_count > USB_SPI_MAX_WRITE_COUNT) { usb_spi_state.status_code = USB_SPI_WRITE_COUNT_INVALID; return; } usb_spi_setup_transfer(write_enable + opcode_count + addr_count + write_count, 0); } else { size_t read_count = packet->cmd_flash_start.count; if (read_count > USB_SPI_MAX_READ_COUNT) { usb_spi_state.status_code = USB_SPI_WRITE_COUNT_INVALID; return; } usb_spi_setup_transfer(write_enable + opcode_count + addr_count, read_count); } packet->header_size = offsetof(struct usb_spi_flash_command, data); usb_spi_state.status_code = usb_spi_read_usb_packet(&usb_spi_state.spi_write_ctx, packet); } #endif /* * Process the rx packet. * * @param config USB SPI config * @param packet Received packet to process. */ static void usb_spi_process_rx_packet(struct usb_spi_packet_ctx *packet) { if (packet->packet_size < USB_SPI_MIN_PACKET_SIZE) { /* No valid packet exists smaller than the packet id. */ setup_transfer_response(USB_SPI_RX_UNEXPECTED_PACKET); return; } /* Reset the mode until we've processed the packet. */ usb_spi_state.mode = USB_SPI_MODE_IDLE; switch (packet->packet_id) { case USB_SPI_PKT_ID_CMD_GET_USB_SPI_CONFIG: { /* The host requires the SPI configuration. */ usb_spi_state.mode = USB_SPI_MODE_SEND_CONFIGURATION; break; } case USB_SPI_PKT_ID_CMD_RESTART_RESPONSE: { /* * The host has requested the device restart the last response. * This is used to recover from lost USB packets without * duplicating SPI transfers. */ setup_transfer_response(usb_spi_state.status_code); break; } case USB_SPI_PKT_ID_CMD_TRANSFER_START: { /* The host started a new USB SPI transfer */ size_t write_count = packet->cmd_start.write_count; size_t read_count = packet->cmd_start.read_count; usb_spi_state.flash_flags = 0; if (!usb_spi_state.enabled) { setup_transfer_response(USB_SPI_DISABLED); } else if (write_count > USB_SPI_MAX_WRITE_COUNT) { setup_transfer_response(USB_SPI_WRITE_COUNT_INVALID); #ifdef CONFIG_SPI_HALFDUPLEX } else if (read_count == USB_SPI_FULL_DUPLEX_ENABLED) { /* Full duplex mode is not supported on this device. */ setup_transfer_response( USB_SPI_UNSUPPORTED_FULL_DUPLEX); #endif } else if (read_count > USB_SPI_MAX_READ_COUNT && read_count != USB_SPI_FULL_DUPLEX_ENABLED) { setup_transfer_response(USB_SPI_READ_COUNT_INVALID); } else { usb_spi_setup_transfer(write_count, read_count); packet->header_size = offsetof(struct usb_spi_command_v2, data); usb_spi_state.status_code = usb_spi_read_usb_packet( &usb_spi_state.spi_write_ctx, packet); } /* Send responses if we encountered an error. */ if (usb_spi_state.status_code != USB_SPI_SUCCESS) { setup_transfer_response(usb_spi_state.status_code); break; } /* Start the SPI transfer when we've read all data. */ if (usb_spi_state.spi_write_ctx.transfer_index == usb_spi_state.spi_write_ctx.transfer_size) { usb_spi_state.mode = USB_SPI_MODE_START_SPI; } break; } #ifdef CONFIG_USB_SPI_FLASH_EXTENSIONS case USB_SPI_PKT_ID_CMD_FLASH_TRANSFER_START: { /* The host started a new USB serial flash SPI transfer */ if (!usb_spi_state.enabled) { setup_transfer_response(USB_SPI_DISABLED); } else { setup_flash_transfer(packet); } /* Send responses if we encountered an error. */ if (usb_spi_state.status_code != USB_SPI_SUCCESS) { setup_transfer_response(usb_spi_state.status_code); break; } /* Start the SPI transfer when we've read all data. */ if (usb_spi_state.spi_write_ctx.transfer_index == usb_spi_state.spi_write_ctx.transfer_size) { usb_spi_state.mode = USB_SPI_MODE_START_SPI; } break; } #endif case USB_SPI_PKT_ID_CMD_TRANSFER_CONTINUE: { /* * The host has sent a continue packet for the SPI transfer * which contains additional data payload. */ packet->header_size = offsetof(struct usb_spi_continue_v2, data); if (usb_spi_state.status_code == USB_SPI_SUCCESS) { usb_spi_state.status_code = usb_spi_read_usb_packet( &usb_spi_state.spi_write_ctx, packet); } /* Send responses if we encountered an error. */ if (usb_spi_state.status_code != USB_SPI_SUCCESS) { setup_transfer_response(usb_spi_state.status_code); break; } /* Start the SPI transfer when we've read all data. */ if (usb_spi_state.spi_write_ctx.transfer_index == usb_spi_state.spi_write_ctx.transfer_size) { usb_spi_state.mode = USB_SPI_MODE_START_SPI; } break; } case USB_SPI_PKT_ID_CMD_CHIP_SELECT: { /* * The host is requesting the chip select line be * asserted or deasserted. */ uint16_t flags = packet->cmd_cs.flags; const struct spi_device_t *current_device = &spi_devices[usb_spi_state.current_spi_device_idx]; if (flags & USB_SPI_CHIP_SELECT) { /* Set chip select low (asserted). */ gpio_set_level(current_device->gpio_cs, 0); } else { /* Set chip select high (adesserted). */ gpio_set_level(current_device->gpio_cs, 1); } usb_spi_state.mode = USB_SPI_MODE_SEND_CHIP_SELECT_RESPONSE; break; } default: { /* An unknown USB packet was delivered. */ setup_transfer_response(USB_SPI_RX_UNEXPECTED_PACKET); break; } } } /* * Perform a SPI write-then-read transaction, optionally preceded by a single * byte "write enable" (separated by deasserting chip select), and optionally * followed by polling the "busy bit" until clear. */ static uint16_t do_spi_transfer(void) { const struct spi_device_t *current_device = &spi_devices[usb_spi_state.current_spi_device_idx]; bool custom_board_driver = current_device->usb_flags & USB_SPI_CUSTOM_SPI_DEVICE; /* * If CONFIG_USB_SPI_FLASH_EXTENSIONS is not enabled, then the below * value being zero will allow the compiler to optimize away several * large if-blocks in this function. */ const uint32_t flash_flags = IS_ENABLED(CONFIG_USB_SPI_FLASH_EXTENSIONS) ? usb_spi_state.flash_flags : 0; uint16_t status_code = EC_SUCCESS; int read_count = usb_spi_state.spi_read_ctx.transfer_size; const char *write_data_ptr = usb_spi_state.spi_write_ctx.buffer; int write_count = usb_spi_state.spi_write_ctx.transfer_size; #ifndef CONFIG_SPI_HALFDUPLEX /* * Handle the full duplex mode on supported platforms. * The read count is equal to the write count. */ if (read_count == USB_SPI_FULL_DUPLEX_ENABLED) { usb_spi_state.spi_read_ctx.transfer_size = usb_spi_state.spi_write_ctx.transfer_size; read_count = SPI_READBACK_ALL; } #endif if (!custom_board_driver && (flash_flags & FLASH_FLAGS_REQUIRING_SUPPORT)) { /* * The standard spi_transaction() does not support * any multi-lane modes. */ return USB_SPI_UNSUPPORTED_FLASH_MODE; } if (status_code == EC_SUCCESS && flash_flags & FLASH_FLAG_WRITE_ENABLE) { /* Precede main transaction with one-byte "write enable". */ if (custom_board_driver) { status_code = usb_spi_board_transaction( current_device, 0, write_data_ptr, 1, NULL, 0); } else { status_code = spi_transaction( current_device, write_data_ptr, 1, NULL, 0); } write_data_ptr += 1; write_count -= 1; } if (status_code == EC_SUCCESS) { if (custom_board_driver) { status_code = usb_spi_board_transaction( current_device, flash_flags, write_data_ptr, write_count, usb_spi_state.spi_read_ctx.buffer, read_count); } else { status_code = spi_transaction( current_device, write_data_ptr, write_count, usb_spi_state.spi_read_ctx.buffer, read_count); } } if (flash_flags & FLASH_FLAG_POLL) { /* After main transaction, poll until no longer "busy". */ static timestamp_t deadline; deadline.val = get_time().val + FLASH_BUSY_POLL_TIMEOUT_USEC; while (status_code == EC_SUCCESS) { timestamp_t now; uint8_t status_byte; if (custom_board_driver) { status_code = usb_spi_board_transaction( current_device, 0, &JEDEC_READ_STATUS, 1, &status_byte, 1); } else { status_code = spi_transaction( current_device, &JEDEC_READ_STATUS, 1, &status_byte, 1); } if ((status_byte & JEDEC_STATUS_BUSY) == 0) break; now = get_time(); if (timestamp_expired(deadline, &now)) { status_code = EC_ERROR_TIMEOUT; break; } } } return usb_spi_map_error(status_code); } /* Deferred function to handle state changes, process USB SPI packets, * and construct responses. * * @param config USB SPI config */ void usb_spi_deferred(void) { int enabled; struct usb_spi_packet_ctx *receive_packet = &usb_spi_state.receive_packet; struct usb_spi_packet_ctx *transmit_packet = &usb_spi_state.transmit_packet; transmit_packet->packet_size = 0; if (IS_ENABLED(CONFIG_USB_SPI_IGNORE_HOST_SIDE_ENABLE)) enabled = usb_spi_state.enabled_device; else enabled = usb_spi_state.enabled_device && usb_spi_state.enabled_host; /* * If our overall enabled state has changed we call the board specific * enable or disable routines and save our new state. */ if (enabled != usb_spi_state.enabled) { if (enabled) usb_spi_board_enable(); else usb_spi_board_disable(); usb_spi_state.enabled = enabled; } /* Read any packets from the endpoint. */ usb_spi_read_packet(receive_packet); if (receive_packet->packet_size) { usb_spi_process_rx_packet(receive_packet); } /* Need to send the USB SPI configuration */ if (usb_spi_state.mode == USB_SPI_MODE_SEND_CONFIGURATION) { create_spi_config_response(transmit_packet); usb_spi_write_packet(transmit_packet); usb_spi_state.mode = USB_SPI_MODE_IDLE; return; } /* Need to send response to USB SPI chip select. */ if (usb_spi_state.mode == USB_SPI_MODE_SEND_CHIP_SELECT_RESPONSE) { create_spi_chip_select_response(transmit_packet); usb_spi_write_packet(transmit_packet); usb_spi_state.mode = USB_SPI_MODE_IDLE; return; } /* Start a new SPI transfer. */ if (usb_spi_state.mode == USB_SPI_MODE_START_SPI) { uint16_t status_code = do_spi_transfer(); setup_transfer_response(status_code); } if (usb_spi_response_in_progress() && usb_spi_transmitted_packet()) { usb_spi_create_spi_transfer_response(transmit_packet); usb_spi_write_packet(transmit_packet); } } /* * Sets which SPI modes will be enabled * * @param config USB SPI config * @param enabled usb_spi_request indicating which SPI mode is enabled. */ void usb_spi_enable(int enabled) { usb_spi_state.enabled_device = enabled; hook_call_deferred(usb_spi.deferred, 0); } /* * STM32 Platform: Receive the data from the endpoint into the packet and * mark the endpoint as ready to accept more data. * * @param config USB SPI config * @param packet Destination packet used to store the endpoint data. */ static void usb_spi_read_packet(struct usb_spi_packet_ctx *packet) { size_t packet_size; if (!usb_spi_received_packet()) { /* No data is present on the endpoint. */ packet->packet_size = 0; return; } /* Copy bytes from endpoint memory. */ packet_size = btable_ep[usb_spi.endpoint].rx_count & RX_COUNT_MASK; memcpy_from_usbram(packet->bytes, (void *)usb_sram_addr(usb_spi.ep_rx_ram), packet_size); packet->packet_size = packet_size; /* Set endpoint as valid for accepting new packet. */ STM32_TOGGLE_EP(usb_spi.endpoint, EP_RX_MASK, EP_RX_VALID, 0); } /* * STM32 Platform: Transmit data from the packet to the endpoint buffer. * If a packet is written, the endpoint will be marked valid for transmitting. * * @param config USB SPI config * @param packet Source packet we will write to the endpoint data. */ static void usb_spi_write_packet(struct usb_spi_packet_ctx *packet) { if (packet->packet_size == 0) return; /* Copy bytes to endpoint memory. */ memcpy_to_usbram((void *)usb_sram_addr(usb_spi.ep_tx_ram), packet->bytes, packet->packet_size); btable_ep[usb_spi.endpoint].tx_count = packet->packet_size; /* Mark the packet as having no data. */ packet->packet_size = 0; /* Set endpoint as valid for transmitting new packet. */ STM32_TOGGLE_EP(usb_spi.endpoint, EP_TX_MASK, EP_TX_VALID, 0); } /* * STM32 Platform: Returns the RX endpoint status * * @param config USB SPI config * * @returns Returns true when the RX endpoint has a packet. */ static bool usb_spi_received_packet(void) { return (STM32_USB_EP(usb_spi.endpoint) & EP_RX_MASK) != EP_RX_VALID; } /* STM32 Platform: Returns the TX endpoint status * * @param config USB SPI config * * @returns Returns true when the TX endpoint transmitted * the packet written. */ static bool usb_spi_transmitted_packet(void) { return (STM32_USB_EP(usb_spi.endpoint) & EP_TX_MASK) != EP_TX_VALID; } /* STM32 Platform: Handle interrupt for USB data received. * * @param config USB SPI config */ void usb_spi_rx(void) { /* * We need to set both the TX and RX endpoints to NAK to prevent * transfers. The protocol requires responses to follow a command, but * the USB host will request the next packet from the TX endpoint * before the USB SPI has updated the memory in the buffer. By setting * it to NAK in the ISR, it will not perform a transfer until the * next packet is ready. * * This has a side effect of disabling the endpoint interrupts until * they are set to valid or a USB reset events occurs. */ STM32_TOGGLE_EP(usb_spi.endpoint, EP_TX_RX_MASK, EP_TX_RX_NAK, 0); hook_call_deferred(usb_spi.deferred, 0); } /* * STM32 Platform: Handle interrupt for USB data transmitted. * * @param config USB SPI config */ void usb_spi_tx(void) { STM32_TOGGLE_EP(usb_spi.endpoint, EP_TX_MASK, EP_TX_NAK, 0); hook_call_deferred(usb_spi.deferred, 0); } /* * STM32 Platform: Handle interrupt for USB events * * @param config USB SPI config * @param evt USB event */ void usb_spi_event(enum usb_ep_event evt) { int endpoint; if (evt != USB_EVENT_RESET) return; endpoint = usb_spi.endpoint; usb_spi_reset_interface(); btable_ep[endpoint].tx_addr = usb_sram_addr(usb_spi.ep_tx_ram); btable_ep[endpoint].tx_count = 0; btable_ep[endpoint].rx_addr = usb_sram_addr(usb_spi.ep_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 */ } /* * STM32 Platform: Handle control transfers. * * @param config USB SPI config * @param rx_buf Contains setup packet * @param tx_buf unused */ int usb_spi_interface(usb_uint *rx_buf, usb_uint *tx_buf) { struct usb_setup_packet setup; usb_read_setup_packet(rx_buf, &setup); if (setup.bmRequestType != (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE)) return 1; if (setup.wValue >= spi_devices_used || !(spi_devices[setup.wValue].usb_flags & USB_SPI_ENABLED) || setup.wIndex != usb_spi.interface || setup.wLength != 0) return 1; /* Record which SPI device the host wished to manipulate. */ usb_spi_state.current_spi_device_idx = setup.wValue; switch (setup.bRequest) { case USB_SPI_REQ_ENABLE: usb_spi_state.enabled_host = 1; break; case USB_SPI_REQ_DISABLE: usb_spi_state.enabled_host = 0; break; default: return 1; } /* * Our state has changed, call the deferred function to handle the * state change. */ if (!IS_ENABLED(CONFIG_USB_SPI_IGNORE_HOST_SIDE_ENABLE)) hook_call_deferred(usb_spi.deferred, 0); usb_spi_reset_interface(); btable_ep[0].tx_count = 0; STM32_TOGGLE_EP(0, EP_TX_RX_MASK, EP_TX_RX_VALID, EP_STATUS_OUT); return 0; } __overridable int usb_spi_board_transaction(const struct spi_device_t *spi_device, uint32_t flash_flags, const uint8_t *txdata, int txlen, uint8_t *rxdata, int rxlen) { return EC_ERROR_UNIMPLEMENTED; } /* * Register this file with the low-level USB driver. */ const struct usb_interface_descriptor USB_IFACE_DESC(USB_IFACE_SPI) = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = USB_IFACE_SPI, .bAlternateSetting = 0, .bNumEndpoints = 2, .bInterfaceClass = USB_CLASS_VENDOR_SPEC, .bInterfaceSubClass = USB_SUBCLASS_GOOGLE_SPI, .bInterfaceProtocol = USB_PROTOCOL_GOOGLE_SPI, .iInterface = USB_STR_SPI_NAME, }; const struct usb_endpoint_descriptor USB_EP_DESC(USB_IFACE_SPI, 0) = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x80 | USB_EP_SPI, .bmAttributes = 0x02 /* Bulk IN */, .wMaxPacketSize = USB_MAX_PACKET_SIZE, .bInterval = 10, }; const struct usb_endpoint_descriptor USB_EP_DESC(USB_IFACE_SPI, 1) = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_EP_SPI, .bmAttributes = 0x02 /* Bulk OUT */, .wMaxPacketSize = USB_MAX_PACKET_SIZE, .bInterval = 0, }; USB_DECLARE_EP(USB_EP_SPI, usb_spi_tx, usb_spi_rx, usb_spi_event); USB_DECLARE_IFACE(USB_IFACE_SPI, usb_spi_interface);