Files
BL616-firmware/src/uart_bridge.c
T

514 lines
15 KiB
C
Raw Normal View History

/*
* 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);
}