blob: 5ecf0f987277f0b7d2333ae026c267fdbf3095bc [file] [log] [blame]
/* 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 "atomic.h"
#include "common.h"
#include "config.h"
#include "link_defs.h"
#include "printf.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "usart.h"
#include "usb-stream.h"
#include "usb_hw.h"
#include "util.h"
static size_t rx_read(struct usb_stream_config const *config)
{
uintptr_t address = btable_ep[config->endpoint].rx_addr;
size_t count = btable_ep[config->endpoint].rx_count & RX_COUNT_MASK;
/*
* Only read the received USB packet if there is enough space in the
* receive queue.
*/
if (count > queue_space(config->producer.queue))
return 0;
return queue_add_memcpy(config->producer.queue, (void *)address, count,
memcpy_from_usbram);
}
static size_t tx_write(struct usb_stream_config const *config)
{
if (is_queue_buffered(config->consumer.queue) &&
!(config->state->flags & USB_STREAM_TX_FLUSH) &&
queue_count(config->consumer.queue) < config->tx_size) {
/*
* Producer has declared that it uses flush(), but has not yet
* requested flushing of the data in queue, and there is not
* enough to fill a USB packet, so wait for either getting more
* data or an explicit flush().
*/
btable_ep[config->endpoint].tx_count = 0;
return 0;
}
uintptr_t address = btable_ep[config->endpoint].tx_addr;
size_t count = queue_remove_memcpy(config->consumer.queue,
(void *)address, config->tx_size,
memcpy_to_usbram);
btable_ep[config->endpoint].tx_count = count;
if (count < config->tx_size) {
/* Queue is empty, we are done flushing. */
config->state->flags &= ~USB_STREAM_TX_FLUSH;
}
return count;
}
static int tx_valid(struct usb_stream_config const *config)
{
return (STM32_USB_EP(config->endpoint) & EP_TX_MASK) == EP_TX_VALID;
}
static int rx_valid(struct usb_stream_config const *config)
{
return (STM32_USB_EP(config->endpoint) & EP_RX_MASK) == EP_RX_VALID;
}
static void usb_read(struct producer const *producer, size_t count)
{
struct usb_stream_config const *config =
DOWNCAST(producer, struct usb_stream_config, producer);
hook_call_deferred(config->deferred, 0);
}
static void usb_written(struct consumer const *consumer, size_t count)
{
struct usb_stream_config const *config =
DOWNCAST(consumer, struct usb_stream_config, consumer);
if (is_queue_buffered(config->consumer.queue) && count == 0) {
/*
* This is how the producer requests flushing of the queue. We
* set TX_FLUSH, which will be cleared once the queue is empty.
*/
config->state->flags |= USB_STREAM_TX_FLUSH;
}
hook_call_deferred(config->deferred, 0);
}
struct producer_ops const usb_stream_producer_ops = {
.read = usb_read,
};
struct consumer_ops const usb_stream_consumer_ops = {
.written = usb_written,
};
void usb_usart_clear(struct usb_stream_config const *config,
struct usart_config const *usart,
enum clear_which_fifo which)
{
if (which & CLEAR_TX_FIFO) {
/*
* Drop data queued to be transmitted on UART, that is, which
* was received via USB.
*/
usb_stream_clear_rx(config);
}
usart_clear_fifos(usart, which);
if (which & CLEAR_RX_FIFO) {
/*
* Drop data which was received from UART, that is, which has
* been queued to be transmitted via USB.
*/
usb_stream_clear_tx(config);
}
}
void usb_stream_clear_rx(struct usb_stream_config const *config)
{
/* Remove any data in the queue received via USB. */
queue_advance_head(config->producer.queue, (size_t)-1);
}
void usb_stream_clear_tx(struct usb_stream_config const *config)
{
/* Remove any data in the queue for USB transmission. */
queue_advance_head(config->consumer.queue, (size_t)-1);
/* Revoke any data already in USB memory marked as ready for sending. */
STM32_TOGGLE_EP(config->endpoint, EP_TX_MASK, EP_TX_NAK, 0);
}
void usb_stream_deferred(struct usb_stream_config const *config)
{
if (!tx_valid(config) && tx_write(config))
STM32_TOGGLE_EP(config->endpoint, EP_TX_MASK, EP_TX_VALID, 0);
if (!rx_valid(config) && rx_read(config))
STM32_TOGGLE_EP(config->endpoint, EP_RX_MASK, EP_RX_VALID, 0);
}
void usb_stream_tx(struct usb_stream_config const *config)
{
STM32_TOGGLE_EP(config->endpoint, 0, 0, 0);
hook_call_deferred(config->deferred, 0);
}
void usb_stream_rx(struct usb_stream_config const *config)
{
STM32_TOGGLE_EP(config->endpoint, 0, 0, 0);
hook_call_deferred(config->deferred, 0);
}
void usb_stream_event(struct usb_stream_config const *config,
enum usb_ep_event evt)
{
int i;
if (evt != USB_EVENT_RESET)
return;
i = config->endpoint;
btable_ep[i].tx_addr = usb_sram_addr(config->tx_ram);
btable_ep[i].tx_count = 0;
btable_ep[i].rx_addr = usb_sram_addr(config->rx_ram);
btable_ep[i].rx_count = usb_ep_rx_size(config->rx_size);
STM32_USB_EP(i) = ((i << 0) | /* Endpoint Addr*/
(tx_write(config) ? EP_TX_VALID : EP_TX_NAK) |
(0 << 9) | /* Bulk EP */
EP_RX_VALID);
}
int usb_usart_interface(struct usb_stream_config const *config,
struct usart_config const *usart, int interface,
usb_uint *rx_buf, usb_uint *tx_buf)
{
struct usb_setup_packet req;
usb_read_setup_packet(rx_buf, &req);
if (req.bmRequestType ==
(USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE)) {
uint16_t response;
switch (req.bRequest) {
/* Get current parity setting. */
case USB_USART_REQ_PARITY:
response = usart_get_parity(usart);
break;
/* Get current baud rate. */
case USB_USART_REQ_BAUD:
response = DIV_ROUND_NEAREST(usart_get_baud(usart) + 50,
100);
break;
default:
return -1;
}
memcpy_to_usbram((void *)usb_sram_addr(tx_buf), &response,
sizeof(response));
btable_ep[0].tx_count = sizeof(response);
STM32_TOGGLE_EP(0, EP_TX_RX_MASK, EP_TX_RX_VALID, 0);
return 0;
}
if (req.bmRequestType !=
(USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE))
return -1;
if (req.wIndex != interface || req.wLength != 0)
return -1;
switch (req.bRequest) {
/* Set parity. */
case USB_USART_SET_PARITY:
usart_set_parity(usart, req.wValue);
break;
/* Set baud rate. */
case USB_USART_SET_BAUD:
usart_set_baud(usart, req.wValue * 100);
break;
/* Start or end break condition on the TX wire. */
case USB_USART_BREAK:
if (req.wValue == 0xFFFF) {
/* Start indefinite break condition. */
usart_set_break(usart, true);
} else if (req.wValue == 0) {
/* End break condition. */
usart_set_break(usart, false);
} else {
/*
* Other values reserved for future support for pulse
* of particular length.
*/
return -1;
}
break;
/* Discard all queued inbound and/or outbound data. */
case USB_USART_CLEAR_QUEUES:
usb_usart_clear(config, usart,
(enum clear_which_fifo)req.wValue);
break;
default:
return -1;
}
btable_ep[0].tx_count = 0;
STM32_TOGGLE_EP(0, EP_TX_RX_MASK, EP_TX_RX_VALID, EP_STATUS_OUT);
return 0;
}