514 lines
15 KiB
C
514 lines
15 KiB
C
/*
|
|||
|
|
* CDC-ACM <-> UART1 bridge.
|
||
|
|
*
|
||
|
|
* FPGA -> PC: UART1 RX is drained byte by byte by a circular DMA into a
|
||
|
|
* 64 KB ring, so nothing is lost no matter how late the CPU looks at it.
|
||
|
|
* The 8 kHz tick and the IN-complete interrupt move whatever is new to the
|
||
|
|
* CDC IN endpoint. When the host stops reading, the oldest data is dropped.
|
||
|
|
*
|
||
|
|
* PC -> FPGA: OUT packets land in a queue of 512 byte buffers that a second
|
||
|
|
* DMA channel feeds to the UART TX FIFO. When the queue is full the OUT
|
||
|
|
* endpoint is simply not re-armed: the host gets NAKs, nothing is lost.
|
||
|
|
*
|
||
|
|
* All state below is touched only from interrupt context (USB, DMA, timer),
|
||
|
|
* which all run at the same level and therefore never preempt each other.
|
||
|
|
*/
|
||
|
|
#include <string.h>
|
||
|
|
#include "bflb_uart.h"
|
||
|
|
#include "bflb_gpio.h"
|
||
|
|
#include "bflb_dma.h"
|
||
|
|
#include "bflb_clock.h"
|
||
|
|
#include "bflb_irq.h"
|
||
|
|
#include "bl616_glb.h"
|
||
|
|
#include "hardware/uart_reg.h"
|
||
|
|
#include "hardware/dma_reg.h"
|
||
|
|
#include "usbd_core.h"
|
||
|
|
#include "usbd_cdc_acm.h"
|
||
|
|
#include "usb_desc.h"
|
||
|
|
#include "uart_bridge.h"
|
||
|
|
#include "sys.h"
|
||
|
|
#include "hw.h"
|
||
|
|
|
||
|
|
#define RX_RING_SIZE (64u * 1024u) /* power of two */
|
||
|
|
#define RX_RING_MASK (RX_RING_SIZE - 1)
|
||
|
|
#define RX_SEG_SIZE 2048u
|
||
|
|
#define RX_SEG_NUM (RX_RING_SIZE / RX_SEG_SIZE)
|
||
|
|
#define RX_GUARD (8u * 1024u)
|
||
|
|
#define IN_CHUNK_MAX 2048u
|
||
|
|
|
||
|
|
/*
|
||
|
|
* One buffer per USB packet: usbser.sys writes in multiples of the packet
|
||
|
|
* size without a terminating zero length packet, so a read longer than one
|
||
|
|
* packet could wait forever for the end of a transfer.
|
||
|
|
*/
|
||
|
|
#define TX_BUF_NUM 32u
|
||
|
|
#define TX_BUF_SIZE 512u
|
||
|
|
|
||
|
|
/* 160 MHz / 8 = 20 Mbaud is the fastest setting accepted */
|
||
|
|
#define UART_DIV_MIN 8u
|
||
|
|
|
||
|
|
#define TICK_HZ 8000u /* uart_bridge_tick() rate */
|
||
|
|
|
||
|
|
static ATTR_NOCACHE_NOINIT_RAM_SECTION __attribute__((aligned(64))) uint8_t rx_ring[RX_RING_SIZE];
|
||
|
|
static ATTR_NOCACHE_NOINIT_RAM_SECTION __attribute__((aligned(16))) struct bflb_dma_channel_lli_pool_s rx_lli[RX_SEG_NUM];
|
||
|
|
static ATTR_NOCACHE_NOINIT_RAM_SECTION __attribute__((aligned(16))) struct bflb_dma_channel_lli_pool_s tx_lli[1];
|
||
|
|
/* the USB VDMA wants aligned buffers, so IN data goes through a bounce buffer */
|
||
|
|
static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t in_buf[2][IN_CHUNK_MAX];
|
||
|
|
static uint8_t in_buf_idx;
|
||
|
|
static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t tx_buf[TX_BUF_NUM][TX_BUF_SIZE];
|
||
|
|
|
||
|
|
static struct bflb_device_s *uart;
|
||
|
|
static struct bflb_device_s *rx_dma;
|
||
|
|
static struct bflb_device_s *tx_dma;
|
||
|
|
static uintptr_t uart_base;
|
||
|
|
static uint32_t uart_clk;
|
||
|
|
static volatile uint32_t *rx_dma_dst;
|
||
|
|
|
||
|
|
/* FPGA -> PC */
|
||
|
|
static uint32_t rx_rd; /* ring offset of the oldest byte not yet sent */
|
||
|
|
static uint32_t rx_last_wr; /* DMA write offset seen by the last update */
|
||
|
|
static uint32_t rx_pending; /* bytes between rx_rd and rx_last_wr */
|
||
|
|
static bool in_busy;
|
||
|
|
static uint16_t in_mps; /* CDC IN/OUT max packet size of the current connection */
|
||
|
|
static uint16_t out_mps;
|
||
|
|
|
||
|
|
/* PC -> FPGA: buffers [tx_rd, tx_wr) are full, tx_wr is the one being filled */
|
||
|
|
static uint16_t tx_len[TX_BUF_NUM];
|
||
|
|
static uint32_t tx_wr, tx_rd;
|
||
|
|
static bool tx_dma_busy;
|
||
|
|
static bool out_armed;
|
||
|
|
|
||
|
|
static bool configured;
|
||
|
|
static bool dtr;
|
||
|
|
static struct cdc_line_coding line_coding = { 115200, 0, 0, 8 };
|
||
|
|
static struct uart_stats stats;
|
||
|
|
|
||
|
|
static uint32_t rx_wd_limit;
|
||
|
|
static uint32_t rx_wd_ticks;
|
||
|
|
static uint32_t rx_wd_bytes;
|
||
|
|
|
||
|
|
static void tx_dma_done(void *arg);
|
||
|
|
|
||
|
|
static void dma_dummy_isr(void *arg)
|
||
|
|
{
|
||
|
|
(void)arg;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------- UART */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* (Re)configure UART1 from scratch. The BL616 receiver can lock up for good
|
||
|
|
* (URX busy, no more bytes) when its bit period changes in the middle of an
|
||
|
|
* incoming frame; clearing/setting the enable bits, as the SDK does, does not
|
||
|
|
* get it out. A peripheral reset does, so every real change of the line
|
||
|
|
* parameters goes through one. The DMA channels are left running: they just
|
||
|
|
* wait for the next request.
|
||
|
|
*/
|
||
|
|
static void uart_apply_line_coding(const struct cdc_line_coding *lc)
|
||
|
|
{
|
||
|
|
uint32_t baud = lc->dwDTERate ? lc->dwDTERate : 115200;
|
||
|
|
uint32_t div = (uart_clk + baud / 2) / baud;
|
||
|
|
uint32_t bits = lc->bDataBits;
|
||
|
|
uint32_t tx, rx, fifo;
|
||
|
|
|
||
|
|
if (div < UART_DIV_MIN) {
|
||
|
|
div = UART_DIV_MIN;
|
||
|
|
}
|
||
|
|
if (div > 0x10000) {
|
||
|
|
div = 0x10000;
|
||
|
|
}
|
||
|
|
if (bits < 5 || bits > 8) {
|
||
|
|
bits = 8;
|
||
|
|
}
|
||
|
|
|
||
|
|
GLB_AHB_MCU_Software_Reset(GLB_AHB_MCU_SW_UART1);
|
||
|
|
|
||
|
|
putreg32(((div - 1) << 16) | (div - 1), uart_base + UART_BIT_PRD_OFFSET);
|
||
|
|
putreg32(0, uart_base + UART_DATA_CONFIG_OFFSET);
|
||
|
|
putreg32(0xFFFFFFFF, uart_base + UART_INT_MASK_OFFSET);
|
||
|
|
|
||
|
|
/* DMA requests as soon as there is one byte / one free slot */
|
||
|
|
fifo = getreg32(uart_base + UART_FIFO_CONFIG_1_OFFSET);
|
||
|
|
fifo &= ~(UART_TX_FIFO_TH_MASK | UART_RX_FIFO_TH_MASK);
|
||
|
|
putreg32(fifo, uart_base + UART_FIFO_CONFIG_1_OFFSET);
|
||
|
|
|
||
|
|
tx = getreg32(uart_base + UART_UTX_CONFIG_OFFSET);
|
||
|
|
rx = getreg32(uart_base + UART_URX_CONFIG_OFFSET);
|
||
|
|
tx |= UART_CR_UTX_FRM_EN;
|
||
|
|
tx &= ~(UART_CR_UTX_BIT_CNT_D_MASK | UART_CR_UTX_BIT_CNT_P_MASK |
|
||
|
|
UART_CR_UTX_PRT_EN | UART_CR_UTX_PRT_SEL);
|
||
|
|
rx &= ~(UART_CR_URX_BIT_CNT_D_MASK | UART_CR_URX_PRT_EN | UART_CR_URX_PRT_SEL);
|
||
|
|
|
||
|
|
tx |= (bits - 1) << UART_CR_UTX_BIT_CNT_D_SHIFT;
|
||
|
|
rx |= (bits - 1) << UART_CR_URX_BIT_CNT_D_SHIFT;
|
||
|
|
|
||
|
|
/* stop bits field counts half bits minus one: 1 -> 1, 1.5 -> 2, 2 -> 3 */
|
||
|
|
switch (lc->bCharFormat) {
|
||
|
|
case 1:
|
||
|
|
tx |= UART_STOP_BITS_1_5 << UART_CR_UTX_BIT_CNT_P_SHIFT;
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
tx |= UART_STOP_BITS_2 << UART_CR_UTX_BIT_CNT_P_SHIFT;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
tx |= UART_STOP_BITS_1 << UART_CR_UTX_BIT_CNT_P_SHIFT;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* mark/space parity is not supported by the hardware: sent as none */
|
||
|
|
if (lc->bParityType == 1) {
|
||
|
|
tx |= UART_CR_UTX_PRT_EN | UART_CR_UTX_PRT_SEL;
|
||
|
|
rx |= UART_CR_URX_PRT_EN | UART_CR_URX_PRT_SEL;
|
||
|
|
} else if (lc->bParityType == 2) {
|
||
|
|
tx |= UART_CR_UTX_PRT_EN;
|
||
|
|
rx |= UART_CR_URX_PRT_EN;
|
||
|
|
}
|
||
|
|
|
||
|
|
putreg32(UART_DMA_TX_EN | UART_DMA_RX_EN, uart_base + UART_FIFO_CONFIG_0_OFFSET);
|
||
|
|
putreg32(tx | UART_CR_UTX_EN, uart_base + UART_UTX_CONFIG_OFFSET);
|
||
|
|
putreg32(rx | UART_CR_URX_EN, uart_base + UART_URX_CONFIG_OFFSET);
|
||
|
|
|
||
|
|
stats.baud_requested = baud;
|
||
|
|
stats.baud_actual = uart_clk / div;
|
||
|
|
|
||
|
|
/* receiver watchdog: 100 ms plus 30 frame times */
|
||
|
|
rx_wd_limit = TICK_HZ / 10 + (TICK_HZ * 300u) / baud;
|
||
|
|
rx_wd_ticks = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* receiver busy, yet no byte for a long time: it is stuck, reset it */
|
||
|
|
static void rx_watchdog(void)
|
||
|
|
{
|
||
|
|
if ((getreg32(uart_base + UART_STATUS_OFFSET) & UART_STS_URX_BUS_BUSY) &&
|
||
|
|
stats.rx_bytes == rx_wd_bytes) {
|
||
|
|
if (++rx_wd_ticks > rx_wd_limit) {
|
||
|
|
uart_apply_line_coding(&line_coding);
|
||
|
|
stats.rx_resets++;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
rx_wd_bytes = stats.rx_bytes;
|
||
|
|
rx_wd_ticks = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void uart_bridge_init(void)
|
||
|
|
{
|
||
|
|
struct bflb_device_s *gpio = bflb_device_get_by_name("gpio");
|
||
|
|
struct bflb_uart_config_s cfg = {
|
||
|
|
.baudrate = 115200,
|
||
|
|
.direction = UART_DIRECTION_TXRX,
|
||
|
|
.data_bits = UART_DATA_BITS_8,
|
||
|
|
.stop_bits = UART_STOP_BITS_1,
|
||
|
|
.parity = UART_PARITY_NONE,
|
||
|
|
.bit_order = UART_LSB_FIRST,
|
||
|
|
.flow_ctrl = 0,
|
||
|
|
/* DMA requests as soon as there is one byte / one free slot */
|
||
|
|
.tx_fifo_threshold = 0,
|
||
|
|
.rx_fifo_threshold = 0,
|
||
|
|
};
|
||
|
|
|
||
|
|
bflb_gpio_uart_init(gpio, PIN_UART_TX, GPIO_UART_FUNC_UART1_TX);
|
||
|
|
bflb_gpio_uart_init(gpio, PIN_UART_RX, GPIO_UART_FUNC_UART1_RX);
|
||
|
|
|
||
|
|
uart = bflb_device_get_by_name("uart1");
|
||
|
|
bflb_uart_init(uart, &cfg);
|
||
|
|
uart_base = uart->reg_base;
|
||
|
|
uart_clk = bflb_clk_get_peripheral_clock(BFLB_DEVICE_TYPE_UART, 1);
|
||
|
|
uart_apply_line_coding(&line_coding);
|
||
|
|
|
||
|
|
/* RX: endless DMA into the ring */
|
||
|
|
struct bflb_dma_channel_config_s rx_cfg = {
|
||
|
|
.direction = DMA_PERIPH_TO_MEMORY,
|
||
|
|
.src_req = DMA_REQUEST_UART1_RX,
|
||
|
|
.dst_req = DMA_REQUEST_NONE,
|
||
|
|
.src_addr_inc = DMA_ADDR_INCREMENT_DISABLE,
|
||
|
|
.dst_addr_inc = DMA_ADDR_INCREMENT_ENABLE,
|
||
|
|
.src_burst_count = DMA_BURST_INCR1,
|
||
|
|
.dst_burst_count = DMA_BURST_INCR1,
|
||
|
|
.src_width = DMA_DATA_WIDTH_8BIT,
|
||
|
|
.dst_width = DMA_DATA_WIDTH_8BIT,
|
||
|
|
};
|
||
|
|
struct bflb_dma_channel_lli_transfer_s seg[RX_SEG_NUM];
|
||
|
|
|
||
|
|
rx_dma = bflb_device_get_by_name("dma0_ch0");
|
||
|
|
bflb_dma_channel_init(rx_dma, &rx_cfg);
|
||
|
|
/* the shared DMA ISR calls every channel's handler without a NULL check */
|
||
|
|
bflb_dma_channel_irq_attach(rx_dma, dma_dummy_isr, NULL);
|
||
|
|
bflb_dma_channel_tcint_mask(rx_dma, true);
|
||
|
|
|
||
|
|
for (uint32_t i = 0; i < RX_SEG_NUM; i++) {
|
||
|
|
seg[i].src_addr = DMA_ADDR_UART1_RDR;
|
||
|
|
seg[i].dst_addr = (uint32_t)(uintptr_t)&rx_ring[i * RX_SEG_SIZE];
|
||
|
|
seg[i].nbytes = RX_SEG_SIZE;
|
||
|
|
}
|
||
|
|
int used = bflb_dma_channel_lli_reload(rx_dma, rx_lli, RX_SEG_NUM, seg, RX_SEG_NUM);
|
||
|
|
bflb_dma_channel_lli_link_head(rx_dma, rx_lli, used);
|
||
|
|
rx_dma_dst = (volatile uint32_t *)(rx_dma->reg_base + DMA_CxDSTADDR_OFFSET);
|
||
|
|
rx_rd = rx_last_wr = 0;
|
||
|
|
|
||
|
|
/* TX: one DMA transfer per filled buffer */
|
||
|
|
struct bflb_dma_channel_config_s tx_cfg = {
|
||
|
|
.direction = DMA_MEMORY_TO_PERIPH,
|
||
|
|
.src_req = DMA_REQUEST_NONE,
|
||
|
|
.dst_req = DMA_REQUEST_UART1_TX,
|
||
|
|
.src_addr_inc = DMA_ADDR_INCREMENT_ENABLE,
|
||
|
|
.dst_addr_inc = DMA_ADDR_INCREMENT_DISABLE,
|
||
|
|
.src_burst_count = DMA_BURST_INCR1,
|
||
|
|
.dst_burst_count = DMA_BURST_INCR1,
|
||
|
|
.src_width = DMA_DATA_WIDTH_8BIT,
|
||
|
|
.dst_width = DMA_DATA_WIDTH_8BIT,
|
||
|
|
};
|
||
|
|
tx_dma = bflb_device_get_by_name("dma0_ch1");
|
||
|
|
bflb_dma_channel_init(tx_dma, &tx_cfg);
|
||
|
|
bflb_dma_channel_irq_attach(tx_dma, tx_dma_done, NULL);
|
||
|
|
|
||
|
|
bflb_uart_link_rxdma(uart, true);
|
||
|
|
bflb_uart_link_txdma(uart, true);
|
||
|
|
bflb_dma_channel_start(rx_dma);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------------------- FPGA -> PC */
|
||
|
|
|
||
|
|
static inline uint32_t rx_wr_offset(void)
|
||
|
|
{
|
||
|
|
uint32_t off = *rx_dma_dst - (uint32_t)(uintptr_t)rx_ring;
|
||
|
|
|
||
|
|
/* right at the end of the last segment the address points past the ring */
|
||
|
|
return (off >= RX_RING_SIZE) ? 0 : off;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void rx_update(void)
|
||
|
|
{
|
||
|
|
uint32_t wr = rx_wr_offset();
|
||
|
|
uint32_t delta = (wr - rx_last_wr) & RX_RING_MASK;
|
||
|
|
|
||
|
|
if (delta == 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
rx_last_wr = wr;
|
||
|
|
rx_pending += delta;
|
||
|
|
stats.rx_bytes += delta;
|
||
|
|
|
||
|
|
if (rx_pending > RX_RING_SIZE - RX_GUARD) {
|
||
|
|
/* host is not reading: keep the newest half */
|
||
|
|
uint32_t drop = rx_pending - RX_RING_SIZE / 2;
|
||
|
|
rx_rd = (rx_rd + drop) & RX_RING_MASK;
|
||
|
|
rx_pending -= drop;
|
||
|
|
stats.rx_dropped += drop;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void rx_submit(void)
|
||
|
|
{
|
||
|
|
if (in_busy || !configured || rx_pending == 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint32_t n = (rx_pending < IN_CHUNK_MAX) ? rx_pending : IN_CHUNK_MAX;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Never end a transfer on a packet boundary: the host only hands data to
|
||
|
|
* the application after a short packet. The usual cure, a zero length
|
||
|
|
* packet, is not reliable on BL616 (requested while the last data packet
|
||
|
|
* still sits in the FIFO it gets lost), so keep one byte for the next
|
||
|
|
* transfer instead.
|
||
|
|
*/
|
||
|
|
if (in_mps && (n % in_mps) == 0) {
|
||
|
|
n--;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint32_t first = RX_RING_SIZE - rx_rd;
|
||
|
|
|
||
|
|
if (first > n) {
|
||
|
|
first = n;
|
||
|
|
}
|
||
|
|
/* alternate buffers: never touch the one the last transfer came from */
|
||
|
|
uint8_t *buf = in_buf[in_buf_idx];
|
||
|
|
in_buf_idx ^= 1;
|
||
|
|
|
||
|
|
memcpy(buf, &rx_ring[rx_rd], first);
|
||
|
|
if (n > first) {
|
||
|
|
memcpy(buf + first, &rx_ring[0], n - first);
|
||
|
|
}
|
||
|
|
rx_rd = (rx_rd + n) & RX_RING_MASK;
|
||
|
|
rx_pending -= n;
|
||
|
|
|
||
|
|
in_busy = true;
|
||
|
|
usbd_ep_start_write(0, CDC_IN_EP, buf, n);
|
||
|
|
sys_activity();
|
||
|
|
}
|
||
|
|
|
||
|
|
static void cdc_in_cb(uint8_t busid, uint8_t ep, uint32_t nbytes)
|
||
|
|
{
|
||
|
|
(void)busid;
|
||
|
|
(void)ep;
|
||
|
|
(void)nbytes;
|
||
|
|
|
||
|
|
in_busy = false;
|
||
|
|
rx_update();
|
||
|
|
rx_submit();
|
||
|
|
}
|
||
|
|
|
||
|
|
void uart_bridge_tick(void)
|
||
|
|
{
|
||
|
|
rx_update();
|
||
|
|
rx_watchdog();
|
||
|
|
rx_submit();
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------------------- PC -> FPGA */
|
||
|
|
|
||
|
|
static void out_arm(void)
|
||
|
|
{
|
||
|
|
if (out_armed || !configured) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if ((tx_wr - tx_rd) < TX_BUF_NUM) {
|
||
|
|
out_armed = true;
|
||
|
|
usbd_ep_start_read(0, CDC_OUT_EP, tx_buf[tx_wr % TX_BUF_NUM], out_mps);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void tx_kick(void)
|
||
|
|
{
|
||
|
|
if (tx_dma_busy || tx_rd == tx_wr) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint32_t i = tx_rd % TX_BUF_NUM;
|
||
|
|
struct bflb_dma_channel_lli_transfer_s tr = {
|
||
|
|
.src_addr = (uint32_t)(uintptr_t)tx_buf[i],
|
||
|
|
.dst_addr = DMA_ADDR_UART1_TDR,
|
||
|
|
.nbytes = tx_len[i],
|
||
|
|
};
|
||
|
|
|
||
|
|
tx_dma_busy = true;
|
||
|
|
bflb_dma_channel_lli_reload(tx_dma, tx_lli, 1, &tr, 1);
|
||
|
|
bflb_dma_channel_start(tx_dma);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void tx_dma_done(void *arg)
|
||
|
|
{
|
||
|
|
(void)arg;
|
||
|
|
tx_dma_busy = false;
|
||
|
|
tx_rd++;
|
||
|
|
tx_kick();
|
||
|
|
out_arm();
|
||
|
|
}
|
||
|
|
|
||
|
|
static void cdc_out_cb(uint8_t busid, uint8_t ep, uint32_t nbytes)
|
||
|
|
{
|
||
|
|
(void)busid;
|
||
|
|
(void)ep;
|
||
|
|
|
||
|
|
if (nbytes == 0) {
|
||
|
|
/* zero length packet: the BL616 driver keeps the read armed */
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
out_armed = false;
|
||
|
|
tx_len[tx_wr % TX_BUF_NUM] = (uint16_t)nbytes;
|
||
|
|
tx_wr++;
|
||
|
|
stats.tx_bytes += nbytes;
|
||
|
|
tx_kick();
|
||
|
|
sys_activity();
|
||
|
|
out_arm();
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------- CDC control */
|
||
|
|
|
||
|
|
void usbd_cdc_acm_set_line_coding(uint8_t busid, uint8_t intf, struct cdc_line_coding *lc)
|
||
|
|
{
|
||
|
|
(void)busid;
|
||
|
|
(void)intf;
|
||
|
|
|
||
|
|
if (lc->dwDTERate == UART_MAGIC_ISP_BAUD) {
|
||
|
|
/* "1200 bps touch" style: firmware update without opening the case */
|
||
|
|
sys_request_isp_reboot();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
/* terminals send the same settings again and again: only act on changes */
|
||
|
|
if (memcmp(lc, &line_coding, sizeof(line_coding)) == 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
line_coding = *lc;
|
||
|
|
uart_apply_line_coding(&line_coding);
|
||
|
|
}
|
||
|
|
|
||
|
|
void usbd_cdc_acm_get_line_coding(uint8_t busid, uint8_t intf, struct cdc_line_coding *lc)
|
||
|
|
{
|
||
|
|
(void)busid;
|
||
|
|
(void)intf;
|
||
|
|
*lc = line_coding;
|
||
|
|
}
|
||
|
|
|
||
|
|
void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool state)
|
||
|
|
{
|
||
|
|
(void)busid;
|
||
|
|
(void)intf;
|
||
|
|
|
||
|
|
if (state && !dtr) {
|
||
|
|
/* port just opened: drop what piled up while nobody listened */
|
||
|
|
rx_update();
|
||
|
|
stats.rx_dropped += rx_pending;
|
||
|
|
rx_rd = rx_last_wr;
|
||
|
|
rx_pending = 0;
|
||
|
|
}
|
||
|
|
dtr = state;
|
||
|
|
}
|
||
|
|
|
||
|
|
void usbd_cdc_acm_set_rts(uint8_t busid, uint8_t intf, bool state)
|
||
|
|
{
|
||
|
|
(void)busid;
|
||
|
|
(void)intf;
|
||
|
|
(void)state;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------- USB */
|
||
|
|
|
||
|
|
static struct usbd_interface cdc_intf_comm;
|
||
|
|
static struct usbd_interface cdc_intf_data;
|
||
|
|
|
||
|
|
static struct usbd_endpoint cdc_in_ep = {
|
||
|
|
.ep_addr = CDC_IN_EP,
|
||
|
|
.ep_cb = cdc_in_cb,
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct usbd_endpoint cdc_out_ep = {
|
||
|
|
.ep_addr = CDC_OUT_EP,
|
||
|
|
.ep_cb = cdc_out_cb,
|
||
|
|
};
|
||
|
|
|
||
|
|
void uart_bridge_usb_init(uint8_t busid)
|
||
|
|
{
|
||
|
|
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &cdc_intf_comm));
|
||
|
|
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &cdc_intf_data));
|
||
|
|
usbd_add_endpoint(busid, &cdc_in_ep);
|
||
|
|
usbd_add_endpoint(busid, &cdc_out_ep);
|
||
|
|
}
|
||
|
|
|
||
|
|
void uart_bridge_on_reset(void)
|
||
|
|
{
|
||
|
|
configured = false;
|
||
|
|
in_busy = false;
|
||
|
|
out_armed = false;
|
||
|
|
dtr = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void uart_bridge_on_configured(void)
|
||
|
|
{
|
||
|
|
configured = true;
|
||
|
|
in_busy = false;
|
||
|
|
in_mps = usbd_get_ep_mps(0, CDC_IN_EP);
|
||
|
|
out_mps = usbd_get_ep_mps(0, CDC_OUT_EP);
|
||
|
|
out_armed = false;
|
||
|
|
out_arm();
|
||
|
|
rx_submit();
|
||
|
|
}
|
||
|
|
|
||
|
|
void uart_bridge_get_stats(struct uart_stats *st)
|
||
|
|
{
|
||
|
|
uintptr_t flag = bflb_irq_save();
|
||
|
|
*st = stats;
|
||
|
|
bflb_irq_restore(flag);
|
||
|
|
}
|