at91sam3x8e: serial refactor

This commit is contained in:
Felix Kopp 2021-01-05 13:45:56 +01:00
parent 316b4c2c0c
commit 1c835a9738
No known key found for this signature in database
GPG key ID: C478BA0A85F75728
5 changed files with 65 additions and 38 deletions
arch/at91sam3x8e
include
arch
at91sam3x8e
serial.h
ardix

View file

@ -38,7 +38,8 @@ ARDIX_SOURCES += \
ARDIX_ASM_SOURCES += \ ARDIX_ASM_SOURCES += \
$(ARDIX_ARCH_PWD)/irq_pend_sv.S \ $(ARDIX_ARCH_PWD)/irq_pend_sv.S \
$(ARDIX_ARCH_PWD)/irq_svc.S $(ARDIX_ARCH_PWD)/irq_svc.S \
$(ARDIX_ARCH_PWD)/syscall.S
CFLAGS += \ CFLAGS += \
-DARCH_AT91SAM3X8E -DARCH_AT91SAM3X8E

View file

@ -3,6 +3,8 @@
#include <ardix/atomic.h> #include <ardix/atomic.h>
#include <ardix/io.h> #include <ardix/io.h>
#include <ardix/list.h>
#include <ardix/malloc.h>
#include <ardix/ringbuf.h> #include <ardix/ringbuf.h>
#include <ardix/serial.h> #include <ardix/serial.h>
#include <ardix/string.h> #include <ardix/string.h>
@ -16,13 +18,14 @@
#include <stddef.h> #include <stddef.h>
struct arch_serial_interface arch_serial_default_interface = { struct arch_serial_interface arch_serial_default_interface = {
.tx_current = NULL,
.tx_next = NULL,
.interface = { .interface = {
.tx = NULL, .tx = NULL,
.rx = NULL, .rx = NULL,
.id = 0, .id = 0,
.baud = 0, .baud = 0,
}, },
.hw_txrdy = false,
}; };
struct serial_interface *serial_default_interface = &arch_serial_default_interface.interface; struct serial_interface *serial_default_interface = &arch_serial_default_interface.interface;
@ -33,8 +36,6 @@ int arch_serial_init(struct serial_interface *interface)
if (interface->baud <= 0 || interface->id != 0) if (interface->baud <= 0 || interface->id != 0)
return -1; return -1;
memset(&arch_iface->txbuf[0], 0, CONFIG_ARCH_SERIAL_BUFSZ);
/* enable peripheral clock for UART (which has peripheral id 8) */ /* enable peripheral clock for UART (which has peripheral id 8) */
REG_PMC_PCER0 |= REG_PMC_PCER0_PID(8); REG_PMC_PCER0 |= REG_PMC_PCER0_PID(8);
@ -57,7 +58,7 @@ int arch_serial_init(struct serial_interface *interface)
/* choose the events we want an interrupt on */ /* choose the events we want an interrupt on */
REG_UART_IDR = 0xFFFFFFFF; /* make sure all interrupts are disabled first */ REG_UART_IDR = 0xFFFFFFFF; /* make sure all interrupts are disabled first */
REG_UART_IER = REG_UART_IER_RXRDY_MASK REG_UART_IER = REG_UART_IER_RXRDY_MASK
| REG_UART_IER_TXBUFE_MASK | REG_UART_IER_ENDTX_MASK
| REG_UART_IER_OVRE_MASK | REG_UART_IER_OVRE_MASK
| REG_UART_IER_FRAME_MASK; | REG_UART_IER_FRAME_MASK;
@ -85,24 +86,37 @@ void arch_serial_exit(struct serial_interface *interface)
interface->id = -1; interface->id = -1;
} }
void io_serial_buf_update(struct serial_interface *interface) ssize_t arch_serial_write(struct serial_interface *interface, const void *buf, size_t len)
{ {
uint16_t len; struct arch_serial_buffer *arch_buf = NULL;
struct arch_serial_interface *arch_iface = to_arch_serial_interface(interface); struct arch_serial_interface *arch_iface = to_arch_serial_interface(interface);
if (arch_iface->hw_txrdy) { if (arch_iface->tx_next != NULL)
atomic_enter(); return -EBUSY;
len = (uint16_t)ringbuf_read(&arch_iface->txbuf[0], interface->tx,
CONFIG_ARCH_SERIAL_BUFSZ);
atomic_leave();
if (len) { if (len >= (1 << 16)) /* DMA uses 16-bit counters */
arch_iface->hw_txrdy = false; len = 0xffff;
REG_UART_IER = REG_UART_IER_TXBUFE_MASK;
REG_UART_PDC_TPR = (uint32_t)&arch_iface->txbuf[0]; arch_buf = malloc(sizeof(*arch_buf) + len);
REG_UART_PDC_TCR = len; if (arch_buf == NULL)
} return -ENOMEM;
memcpy(&arch_buf->data[0], buf, len);
arch_buf->len = (uint16_t)len;
if (arch_iface->tx_current == NULL) {
arch_iface->tx_current = arch_buf;
REG_UART_PDC_TPR = (uint32_t)&arch_buf->data[0];
REG_UART_PDC_TCR = arch_buf->len;
/* we weren't transmitting, so the interrupt was masked */
REG_UART_IER = REG_UART_IER_ENDTX_MASK;
} else {
arch_iface->tx_next = arch_buf;
REG_UART_PDC_TNPR = (uint32_t)&arch_buf->data[0];
REG_UART_PDC_TNCR = arch_buf->len;
} }
return (ssize_t)len;
} }
void irq_uart(void) void irq_uart(void)
@ -116,15 +130,16 @@ void irq_uart(void)
ringbuf_write(arch_serial_default_interface.interface.rx, &tmp, sizeof(tmp)); ringbuf_write(arch_serial_default_interface.interface.rx, &tmp, sizeof(tmp));
} }
/* TX buffer has been sent */ /* REG_UART_PDC_TCR has reached zero */
if (state & REG_UART_SR_TXBUFE_MASK) { if (state & REG_UART_SR_ENDTX_MASK) {
/* free(arch_serial_default_interface.tx_current);
* this is picked up by the I/O thread, which will copy the next
* chunk of data from the ring buffer to the hardware buffer and /* DMA automatically does this to the actual hardware registers */
* resume transmission arch_serial_default_interface.tx_current = arch_serial_default_interface.tx_next;
*/ arch_serial_default_interface.tx_next = NULL;
arch_serial_default_interface.hw_txrdy = true;
REG_UART_IDR = REG_UART_IDR_TXBUFE_MASK; if (arch_serial_default_interface.tx_current == NULL)
REG_UART_IDR = REG_UART_IDR_ENDTX_MASK;
} }
/* check for error conditions */ /* check for error conditions */

View file

@ -13,20 +13,19 @@
#define CONFIG_ARCH_SERIAL_BUFSZ 32 #define CONFIG_ARCH_SERIAL_BUFSZ 32
#endif /* CONFIG_ARCH_SERIAL_BUFSZ */ #endif /* CONFIG_ARCH_SERIAL_BUFSZ */
struct arch_serial_buffer {
uint16_t len;
uint8_t data[];
};
/** Architecture-specific extension of `struct serial_interface` */ /** Architecture-specific extension of `struct serial_interface` */
struct arch_serial_interface { struct arch_serial_interface {
struct serial_interface interface; /** should always match REG_UART_PDC_TPR */
struct arch_serial_buffer *tx_current;
/** should always match REG_UART_PDC_TNPR */
struct arch_serial_buffer *tx_next;
/* struct serial_interface interface;
* two hardware buffers; one is for being written to while the other one can be read from
* by the hardware w/out interfering with each other. `arch_serial_hwbuf_rotate()` is
* responsible for writing this to the respective hardware register and swapping them out.
* The platform's buffer length registers only allow 16-byte numbers, so we can save some
* memory by not using `size_t`
*/
uint8_t txbuf[CONFIG_ARCH_SERIAL_BUFSZ];
/** hardware has finished sending the current buffer and ready for a swap */
bool hw_txrdy;
}; };
/** /**

View file

@ -10,6 +10,17 @@
int arch_serial_init(struct serial_interface *interface); int arch_serial_init(struct serial_interface *interface);
void arch_serial_exit(struct serial_interface *interface); void arch_serial_exit(struct serial_interface *interface);
/**
* Copy `buf` to a hardware buffer in the TX queue.
* The transmission is performed asynchronously.
*
* @param interface: serial interface to enqueue the buffer for
* @param buf: raw buffer data
* @param len: length of `buf`
* @returns actual amount of bytes enqueued, or a negative error code on failure
*/
ssize_t arch_serial_write(struct serial_interface *interface, const void *buf, size_t len);
#include ARCH_INCLUDE(serial.h) #include ARCH_INCLUDE(serial.h)
/* /*

View file

@ -5,6 +5,7 @@
#include <ardix/types.h> #include <ardix/types.h>
#include <ardix/ringbuf.h> #include <ardix/ringbuf.h>
#include <toolchain.h> #include <toolchain.h>
#ifndef CONFIG_SERIAL_BAUD #ifndef CONFIG_SERIAL_BAUD