blob: 54caae015e60fd076a1cb08a22cbfad5c304332f [file] [log] [blame]
/* Copyright 2014 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 "spi.h"
#include "usb_descriptor.h"
#include "usb_hw.h"
#include "usb_spi.h"
#include "util.h"
/* Forward declare platform specific functions. */
static bool usb_spi_received_packet(struct usb_spi_config const *config);
static bool usb_spi_transmitted_packet(struct usb_spi_config const *config);
static void usb_spi_read_packet(struct usb_spi_config const *config,
struct usb_spi_packet_ctx *packet);
static void usb_spi_write_packet(struct usb_spi_config const *config,
struct usb_spi_packet_ctx *packet);
/*
* 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(struct usb_spi_config const *config,
size_t write_count, size_t read_count)
{
/* Reset any status code. */
config->state->status_code = USB_SPI_SUCCESS;
/* Reset the write and read counts. */
config->state->spi_write_ctx.transfer_size = write_count;
config->state->spi_write_ctx.transfer_index = 0;
config->state->spi_read_ctx.transfer_size = read_count;
config->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(struct usb_spi_config const *config)
{
/* Setup a 0 byte transfer to clear the contexts. */
usb_spi_setup_transfer(config, 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(struct usb_spi_config const *config)
{
if ((config->state->mode == USB_SPI_MODE_START_RESPONSE) ||
(config->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(struct usb_spi_config const *config,
uint16_t status_code)
{
config->state->status_code = status_code;
config->state->spi_read_ctx.transfer_index = 0;
config->state->mode = USB_SPI_MODE_START_RESPONSE;
/* If an error occurred, transmit an empty start packet. */
if (status_code != USB_SPI_SUCCESS)
config->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_config const *config,
struct usb_spi_packet_ctx *packet)
{
/* 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
packet->packet_size =
sizeof(struct usb_spi_response_configuration_v2);
}
/*
* 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_config const *config,
struct usb_spi_packet_ctx *transmit_packet)
{
if (!usb_spi_response_in_progress(config))
return;
if (config->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 =
config->state->status_code;
usb_spi_fill_usb_packet(transmit_packet,
&config->state->spi_read_ctx);
} else if (config->state->spi_read_ctx.transfer_index <
config->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 =
config->state->spi_read_ctx.transfer_index;
usb_spi_fill_usb_packet(transmit_packet,
&config->state->spi_read_ctx);
}
if (config->state->spi_read_ctx.transfer_index <
config->state->spi_read_ctx.transfer_size) {
config->state->mode = USB_SPI_MODE_CONTINUE_RESPONSE;
} else {
config->state->mode = USB_SPI_MODE_IDLE;
}
}
/*
* 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_config const *config,
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(config, USB_SPI_RX_UNEXPECTED_PACKET);
return;
}
/* Reset the mode until we've processed the packet. */
config->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. */
config->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(config, config->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;
if (!config->state->enabled) {
setup_transfer_response(config, USB_SPI_DISABLED);
} else if (write_count > USB_SPI_MAX_WRITE_COUNT) {
setup_transfer_response(config,
USB_SPI_WRITE_COUNT_INVALID);
} else if (read_count == USB_SPI_FULL_DUPLEX_ENABLED) {
#ifndef CONFIG_SPI_HALFDUPLEX
/* Full duplex mode is not supported on this device. */
setup_transfer_response(config,
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(config,
USB_SPI_READ_COUNT_INVALID);
} else {
usb_spi_setup_transfer(config, write_count, read_count);
packet->header_size =
offsetof(struct usb_spi_command_v2, data);
config->state->status_code = usb_spi_read_usb_packet(
&config->state->spi_write_ctx, packet);
}
/* Send responses if we encountered an error. */
if (config->state->status_code != USB_SPI_SUCCESS) {
setup_transfer_response(config,
config->state->status_code);
break;
}
/* Start the SPI transfer when we've read all data. */
if (config->state->spi_write_ctx.transfer_index ==
config->state->spi_write_ctx.transfer_size) {
config->state->mode = USB_SPI_MODE_START_SPI;
}
break;
}
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 (config->state->status_code == USB_SPI_SUCCESS) {
config->state->status_code = usb_spi_read_usb_packet(
&config->state->spi_write_ctx, packet);
}
/* Send responses if we encountered an error. */
if (config->state->status_code != USB_SPI_SUCCESS) {
setup_transfer_response(config,
config->state->status_code);
break;
}
/* Start the SPI transfer when we've read all data. */
if (config->state->spi_write_ctx.transfer_index ==
config->state->spi_write_ctx.transfer_size) {
config->state->mode = USB_SPI_MODE_START_SPI;
}
break;
}
default:
{
/* An unknown USB packet was delivered. */
setup_transfer_response(config, USB_SPI_RX_UNEXPECTED_PACKET);
break;
}
}
}
/* Deferred function to handle state changes, process USB SPI packets,
* and construct responses.
*
* @param config USB SPI config
*/
void usb_spi_deferred(struct usb_spi_config const *config)
{
int enabled;
struct usb_spi_packet_ctx *receive_packet =
&config->state->receive_packet;
struct usb_spi_packet_ctx *transmit_packet =
&config->state->transmit_packet;
transmit_packet->packet_size = 0;
if (config->flags & USB_SPI_CONFIG_FLAGS_IGNORE_HOST_SIDE_ENABLE)
enabled = config->state->enabled_device;
else
enabled = config->state->enabled_device &&
config->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 != config->state->enabled) {
if (enabled) usb_spi_board_enable(config);
else usb_spi_board_disable(config);
config->state->enabled = enabled;
}
/* Read any packets from the endpoint. */
usb_spi_read_packet(config, receive_packet);
if (receive_packet->packet_size) {
usb_spi_process_rx_packet(config, receive_packet);
}
/* Need to send the USB SPI configuration */
if (config->state->mode == USB_SPI_MODE_SEND_CONFIGURATION) {
create_spi_config_response(config, transmit_packet);
usb_spi_write_packet(config, transmit_packet);
config->state->mode = USB_SPI_MODE_IDLE;
return;
}
/* Start a new SPI transfer. */
if (config->state->mode == USB_SPI_MODE_START_SPI) {
uint16_t status_code;
int read_count = config->state->spi_read_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) {
config->state->spi_read_ctx.transfer_size =
config->state->spi_write_ctx.transfer_size;
read_count = SPI_READBACK_ALL;
}
#endif
status_code = spi_transaction(SPI_FLASH_DEVICE,
config->state->spi_write_ctx.buffer,
config->state->spi_write_ctx.transfer_size,
config->state->spi_read_ctx.buffer,
read_count);
/* Cast the EC status code to USB SPI and start the response. */
status_code = usb_spi_map_error(status_code);
setup_transfer_response(config, status_code);
}
if (usb_spi_response_in_progress(config) &&
usb_spi_transmitted_packet(config)) {
usb_spi_create_spi_transfer_response(config, transmit_packet);
usb_spi_write_packet(config, 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(struct usb_spi_config const *config, int enabled)
{
config->state->enabled_device = enabled;
hook_call_deferred(config->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_config const *config,
struct usb_spi_packet_ctx *packet)
{
size_t packet_size;
if (!usb_spi_received_packet(config)) {
/* No data is present on the endpoint. */
packet->packet_size = 0;
return;
}
/* Copy bytes from endpoint memory. */
packet_size = btable_ep[config->endpoint].rx_count & RX_COUNT_MASK;
memcpy_from_usbram(packet->bytes,
(void *)usb_sram_addr(config->ep_rx_ram), packet_size);
packet->packet_size = packet_size;
/* Set endpoint as valid for accepting new packet. */
STM32_TOGGLE_EP(config->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_config const *config,
struct usb_spi_packet_ctx *packet)
{
if (packet->packet_size == 0)
return;
/* Copy bytes to endpoint memory. */
memcpy_to_usbram((void *)usb_sram_addr(config->ep_tx_ram),
packet->bytes, packet->packet_size);
btable_ep[config->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(config->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(struct usb_spi_config const *config)
{
return (STM32_USB_EP(config->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(struct usb_spi_config const *config)
{
return (STM32_USB_EP(config->endpoint) & EP_TX_MASK) != EP_TX_VALID;
}
/* STM32 Platform: Handle interrupt for USB data received.
*
* @param config USB SPI config
*/
void usb_spi_rx(struct usb_spi_config const *config)
{
/*
* 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(config->endpoint, EP_TX_RX_MASK, EP_TX_RX_NAK, 0);
hook_call_deferred(config->deferred, 0);
}
/*
* STM32 Platform: Handle interrupt for USB data transmitted.
*
* @param config USB SPI config
*/
void usb_spi_tx(struct usb_spi_config const *config)
{
STM32_TOGGLE_EP(config->endpoint, EP_TX_MASK, EP_TX_NAK, 0);
hook_call_deferred(config->deferred, 0);
}
/*
* STM32 Platform: Handle interrupt for USB events
*
* @param config USB SPI config
* @param evt USB event
*/
void usb_spi_event(struct usb_spi_config const *config, enum usb_ep_event evt)
{
int endpoint;
if (evt != USB_EVENT_RESET)
return;
endpoint = config->endpoint;
usb_spi_reset_interface(config);
btable_ep[endpoint].tx_addr = usb_sram_addr(config->ep_tx_ram);
btable_ep[endpoint].tx_count = 0;
btable_ep[endpoint].rx_addr = usb_sram_addr(config->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(struct usb_spi_config const *config,
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 != 0 ||
setup.wIndex != config->interface ||
setup.wLength != 0)
return 1;
switch (setup.bRequest) {
case USB_SPI_REQ_ENABLE:
config->state->enabled_host = 1;
break;
case USB_SPI_REQ_DISABLE:
config->state->enabled_host = 0;
break;
default: return 1;
}
/*
* Our state has changed, call the deferred function to handle the
* state change.
*/
if (!(config->flags & USB_SPI_CONFIG_FLAGS_IGNORE_HOST_SIDE_ENABLE))
hook_call_deferred(config->deferred, 0);
usb_spi_reset_interface(config);
btable_ep[0].tx_count = 0;
STM32_TOGGLE_EP(0, EP_TX_RX_MASK, EP_TX_RX_VALID, EP_STATUS_OUT);
return 0;
}