summaryrefslogtreecommitdiffstats
path: root/sys/arm
diff options
context:
space:
mode:
Diffstat (limited to 'sys/arm')
-rw-r--r--sys/arm/at91/uart_dev_at91usart.c222
1 files changed, 169 insertions, 53 deletions
diff --git a/sys/arm/at91/uart_dev_at91usart.c b/sys/arm/at91/uart_dev_at91usart.c
index 80f28e9..9bc4dbf 100644
--- a/sys/arm/at91/uart_dev_at91usart.c
+++ b/sys/arm/at91/uart_dev_at91usart.c
@@ -1,6 +1,7 @@
/*-
* Copyright (c) 2005 M. Warner Losh
* Copyright (c) 2005 Olivier Houchard
+ * Copyright (c) 2012 Ian Lepore
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -42,12 +43,18 @@ __FBSDID("$FreeBSD$");
#include <dev/uart/uart_bus.h>
#include <arm/at91/at91_usartreg.h>
#include <arm/at91/at91_pdcreg.h>
+#include <arm/at91/at91_piovar.h>
+#include <arm/at91/at91_pioreg.h>
+#include <arm/at91/at91rm92reg.h>
#include <arm/at91/at91var.h>
#include "uart_if.h"
-#define DEFAULT_RCLK at91_master_clock
-#define USART_BUFFER_SIZE 128
+#define DEFAULT_RCLK at91_master_clock
+#define USART_DEFAULT_FIFO_BYTES 128
+
+#define USART_DCE_CHANGE_BITS (USART_CSR_CTSIC | USART_CSR_DCDIC | \
+ USART_CSR_DSRIC | USART_CSR_RIIC)
/*
* High-level UART interface.
@@ -63,7 +70,8 @@ struct at91_usart_softc {
bus_dma_tag_t tx_tag;
bus_dmamap_t tx_map;
uint32_t flags;
-#define HAS_TIMEOUT 1
+#define HAS_TIMEOUT 0x1
+#define USE_RTS0_WORKAROUND 0x2
bus_dma_tag_t rx_tag;
struct at91_usart_rx ping_pong[2];
struct at91_usart_rx *ping;
@@ -193,6 +201,20 @@ at91_usart_param(struct uart_bas *bas, int baudrate, int databits,
if (DEFAULT_RCLK != 0)
WR4(bas, USART_BRGR, BAUD2DIVISOR(baudrate));
+ /*
+ * Set the receive timeout based on the baud rate. The idea is to
+ * compromise between being responsive on an interactive connection and
+ * giving a bulk data sender a bit of time to queue up a new buffer
+ * without mistaking it for a stopping point in the transmission. For
+ * 19.2kbps and below, use 20 * bit time (2 characters). For faster
+ * connections use 500 microseconds worth of bits.
+ */
+ if (baudrate <= 19200)
+ WR4(bas, USART_RTOR, 20);
+ else
+ WR4(bas, USART_RTOR, baudrate / 2000);
+ WR4(bas, USART_CR, USART_CR_STTTO);
+
/* XXX Need to take possible synchronous mode into account */
return (0);
}
@@ -243,7 +265,7 @@ at91_usart_term(struct uart_bas *bas)
/*
* Put a character of console output (so we do it here polling rather than
- * interrutp driven).
+ * interrupt driven).
*/
static void
at91_usart_putc(struct uart_bas *bas, int c)
@@ -312,9 +334,14 @@ static kobj_method_t at91_usart_methods[] = {
int
at91_usart_bus_probe(struct uart_softc *sc)
{
-
- sc->sc_txfifosz = USART_BUFFER_SIZE;
- sc->sc_rxfifosz = USART_BUFFER_SIZE;
+ int value;
+
+ value = USART_DEFAULT_FIFO_BYTES;
+ resource_int_value(device_get_name(sc->sc_dev),
+ device_get_unit(sc->sc_dev), "fifo_bytes", &value);
+ value = roundup2(value, arm_dcache_align);
+ sc->sc_txfifosz = value;
+ sc->sc_rxfifosz = value;
sc->sc_hwiflow = 0;
return (0);
}
@@ -329,6 +356,41 @@ at91_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
}
static int
+at91_usart_requires_rts0_workaround(struct uart_softc *sc)
+{
+ int value;
+ int unit;
+
+ unit = device_get_unit(sc->sc_dev);
+
+ /*
+ * On the rm9200 chips, the PA21/RTS0 pin is not correctly wired to the
+ * usart device interally (so-called 'erratum 39', but it's 41.14 in rev
+ * I of the manual). This prevents use of the hardware flow control
+ * feature in the usart itself. It also means that if we are to
+ * implement RTS/CTS flow via the tty layer logic, we must use pin PA21
+ * as a gpio and manually manipulate it in at91_usart_bus_setsig(). We
+ * can only safely do so if we've been given permission via a hint,
+ * otherwise we might manipulate a pin that's attached to who-knows-what
+ * and Bad Things could happen.
+ */
+ if (at91_is_rm92() && unit == 1) {
+ value = 0;
+ resource_int_value(device_get_name(sc->sc_dev), unit,
+ "use_rts0_workaround", &value);
+ if (value != 0) {
+ at91_pio_use_gpio(AT91RM92_PIOA_BASE, AT91C_PIO_PA21);
+ at91_pio_gpio_output(AT91RM92_PIOA_BASE,
+ AT91C_PIO_PA21, 1);
+ at91_pio_use_periph_a(AT91RM92_PIOA_BASE,
+ AT91C_PIO_PA20, 0);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+static int
at91_usart_bus_attach(struct uart_softc *sc)
{
int err;
@@ -338,6 +400,9 @@ at91_usart_bus_attach(struct uart_softc *sc)
atsc = (struct at91_usart_softc *)sc;
+ if (at91_usart_requires_rts0_workaround(sc))
+ atsc->flags |= USE_RTS0_WORKAROUND;
+
/*
* See if we have a TIMEOUT bit. We disable all interrupts as
* a side effect. Boot loaders may have enabled them. Since
@@ -427,7 +492,11 @@ at91_usart_bus_attach(struct uart_softc *sc)
} else {
WR4(&sc->sc_bas, USART_IER, USART_CSR_RXRDY);
}
- WR4(&sc->sc_bas, USART_IER, USART_CSR_RXBRK);
+ WR4(&sc->sc_bas, USART_IER, USART_CSR_RXBRK | USART_DCE_CHANGE_BITS);
+
+ /* Prime sc->hwsig with the initial hw line states. */
+ at91_usart_bus_getsig(sc);
+
errout:
return (err);
}
@@ -466,7 +535,9 @@ static int
at91_usart_bus_setsig(struct uart_softc *sc, int sig)
{
uint32_t new, old, cr;
- struct uart_bas *bas;
+ struct at91_usart_softc *atsc;
+
+ atsc = (struct at91_usart_softc *)sc;
do {
old = sc->sc_hwsig;
@@ -476,8 +547,7 @@ at91_usart_bus_setsig(struct uart_softc *sc, int sig)
if (sig & SER_DRTS)
SIGCHG(sig & SER_RTS, new, SER_RTS, SER_DRTS);
} while (!atomic_cmpset_32(&sc->sc_hwsig, old, new));
- bas = &sc->sc_bas;
- uart_lock(sc->sc_hwmtx);
+
cr = 0;
if (new & SER_DTR)
cr |= USART_CR_DTREN;
@@ -487,8 +557,18 @@ at91_usart_bus_setsig(struct uart_softc *sc, int sig)
cr |= USART_CR_RTSEN;
else
cr |= USART_CR_RTSDIS;
- WR4(bas, USART_CR, cr);
+
+ uart_lock(sc->sc_hwmtx);
+ WR4(&sc->sc_bas, USART_CR, cr);
+ if (atsc->flags & USE_RTS0_WORKAROUND) {
+ /* Signal is active-low. */
+ if (new & SER_RTS)
+ at91_pio_gpio_clear(AT91RM92_PIOA_BASE, AT91C_PIO_PA21);
+ else
+ at91_pio_gpio_set(AT91RM92_PIOA_BASE,AT91C_PIO_PA21);
+ }
uart_unlock(sc->sc_hwmtx);
+
return (0);
}
@@ -531,6 +611,15 @@ at91_usart_bus_ipend(struct uart_softc *sc)
atsc = (struct at91_usart_softc *)sc;
uart_lock(sc->sc_hwmtx);
csr = RD4(&sc->sc_bas, USART_CSR);
+
+ if (csr & USART_CSR_OVRE) {
+ WR4(&sc->sc_bas, USART_CR, USART_CR_RSTSTA);
+ ipend |= SER_INT_OVERRUN;
+ }
+
+ if (csr & USART_DCE_CHANGE_BITS)
+ ipend |= SER_INT_SIGCHG;
+
if (csr & USART_CSR_ENDTX) {
bus_dmamap_sync(atsc->tx_tag, atsc->tx_map,
BUS_DMASYNC_POSTWRITE);
@@ -552,36 +641,37 @@ at91_usart_bus_ipend(struct uart_softc *sc)
if (atsc->flags & HAS_TIMEOUT) {
if (csr & USART_CSR_RXBUFF) {
/*
- * We have a buffer overflow. Copy all data from both
- * ping and pong. Insert overflow character. Reset
- * ping and pong and re-enable the PDC to receive
- * characters again.
+ * We have a buffer overflow. Consume data from ping
+ * and give it back to the hardware before worrying
+ * about pong, to minimze data loss. Insert an overrun
+ * marker after the contents of the pong buffer.
*/
+ WR4(&sc->sc_bas, PDC_PTCR, PDC_PTCR_RXTDIS);
bus_dmamap_sync(atsc->rx_tag, atsc->ping->map,
BUS_DMASYNC_POSTREAD);
- bus_dmamap_sync(atsc->rx_tag, atsc->pong->map,
- BUS_DMASYNC_POSTREAD);
for (i = 0; i < sc->sc_rxfifosz; i++)
at91_rx_put(sc, atsc->ping->buffer[i]);
+ bus_dmamap_sync(atsc->rx_tag, atsc->ping->map,
+ BUS_DMASYNC_PREREAD);
+ WR4(&sc->sc_bas, PDC_RPR, atsc->ping->pa);
+ WR4(&sc->sc_bas, PDC_RCR, sc->sc_rxfifosz);
+ WR4(&sc->sc_bas, PDC_PTCR, PDC_PTCR_RXTEN);
+ bus_dmamap_sync(atsc->rx_tag, atsc->pong->map,
+ BUS_DMASYNC_POSTREAD);
for (i = 0; i < sc->sc_rxfifosz; i++)
at91_rx_put(sc, atsc->pong->buffer[i]);
uart_rx_put(sc, UART_STAT_OVERRUN);
- bus_dmamap_sync(atsc->rx_tag, atsc->ping->map,
- BUS_DMASYNC_PREREAD);
bus_dmamap_sync(atsc->rx_tag, atsc->pong->map,
BUS_DMASYNC_PREREAD);
- WR4(&sc->sc_bas, PDC_RPR, atsc->ping->pa);
- WR4(&sc->sc_bas, PDC_RCR, sc->sc_rxfifosz);
WR4(&sc->sc_bas, PDC_RNPR, atsc->pong->pa);
WR4(&sc->sc_bas, PDC_RNCR, sc->sc_rxfifosz);
- WR4(&sc->sc_bas, PDC_PTCR, PDC_PTCR_RXTEN);
ipend |= SER_INT_RXREADY;
} else if (csr & USART_CSR_ENDRX) {
/*
- * Shuffle data from ping of ping pong buffer, but
- * leave current pong in place, as it has become the
- * new ping. We need to copy data and setup the old
- * ping as the new pong when we're done.
+ * Consume data from ping of ping pong buffer, but leave
+ * current pong in place, as it has become the new ping.
+ * We need to copy data and setup the old ping as the
+ * new pong when we're done.
*/
bus_dmamap_sync(atsc->rx_tag, atsc->ping->map,
BUS_DMASYNC_POSTREAD);
@@ -597,25 +687,49 @@ at91_usart_bus_ipend(struct uart_softc *sc)
ipend |= SER_INT_RXREADY;
} else if (csr & USART_CSR_TIMEOUT) {
/*
- * We have one partial buffer. We need to stop the
- * PDC, get the number of characters left and from
- * that compute number of valid characters. We then
- * need to reset ping and pong and reenable the PDC.
- * Not sure if there's a race here at fast baud rates
- * we need to worry about.
+ * On a timeout, one of the following applies:
+ * 1. Two empty buffers. The last received byte exactly
+ * filled a buffer, causing an ENDTX that got
+ * processed earlier; no new bytes have arrived.
+ * 2. Ping buffer contains some data and pong is empty.
+ * This should be the most common timeout condition.
+ * 3. Ping buffer is full and pong is now being filled.
+ * This is exceedingly rare; it can happen only if
+ * the ping buffer is almost full when a timeout is
+ * signaled, and then dataflow resumes and the ping
+ * buffer filled up between the time we read the
+ * status register above and the point where the
+ * RXTDIS takes effect here. Yes, it can happen.
+ * Because dataflow can resume at any time following a
+ * timeout (it may have already resumed before we get
+ * here), it's important to minimize the time the PDC is
+ * disabled -- just long enough to take the ping buffer
+ * out of service (so we can consume it) and install the
+ * pong buffer as the active one. Note that in case 3
+ * the hardware has already done the ping-pong swap.
*/
WR4(&sc->sc_bas, PDC_PTCR, PDC_PTCR_RXTDIS);
+ if (RD4(&sc->sc_bas, PDC_RNCR) == 0) {
+ len = sc->sc_rxfifosz;
+ } else {
+ len = sc->sc_rxfifosz - RD4(&sc->sc_bas, PDC_RCR);
+ WR4(&sc->sc_bas, PDC_RPR, atsc->pong->pa);
+ WR4(&sc->sc_bas, PDC_RCR, sc->sc_rxfifosz);
+ WR4(&sc->sc_bas, PDC_RNCR, 0);
+ }
+ WR4(&sc->sc_bas, USART_CR, USART_CR_STTTO);
+ WR4(&sc->sc_bas, PDC_PTCR, PDC_PTCR_RXTEN);
bus_dmamap_sync(atsc->rx_tag, atsc->ping->map,
BUS_DMASYNC_POSTREAD);
- len = sc->sc_rxfifosz - RD4(&sc->sc_bas, PDC_RCR);
for (i = 0; i < len; i++)
at91_rx_put(sc, atsc->ping->buffer[i]);
bus_dmamap_sync(atsc->rx_tag, atsc->ping->map,
BUS_DMASYNC_PREREAD);
- WR4(&sc->sc_bas, PDC_RPR, atsc->ping->pa);
- WR4(&sc->sc_bas, PDC_RCR, sc->sc_rxfifosz);
- WR4(&sc->sc_bas, USART_CR, USART_CR_STTTO);
- WR4(&sc->sc_bas, PDC_PTCR, PDC_PTCR_RXTEN);
+ p = atsc->ping;
+ atsc->ping = atsc->pong;
+ atsc->pong = p;
+ WR4(&sc->sc_bas, PDC_RNPR, atsc->pong->pa);
+ WR4(&sc->sc_bas, PDC_RNCR, sc->sc_rxfifosz);
ipend |= SER_INT_RXREADY;
}
} else if (csr & USART_CSR_RXRDY) {
@@ -645,22 +759,24 @@ at91_usart_bus_flush(struct uart_softc *sc, int what)
static int
at91_usart_bus_getsig(struct uart_softc *sc)
{
- uint32_t csr, new, sig;
+ uint32_t csr, new, old, sig;
+
+ /*
+ * Note that the atmel channel status register DCE status bits reflect
+ * the electrical state of the lines, not the logical state. Since they
+ * are logically active-low signals, we invert the tests here.
+ */
+ do {
+ old = sc->sc_hwsig;
+ sig = old;
+ csr = RD4(&sc->sc_bas, USART_CSR);
+ SIGCHG(!(csr & USART_CSR_DSR), sig, SER_DSR, SER_DDSR);
+ SIGCHG(!(csr & USART_CSR_CTS), sig, SER_CTS, SER_DCTS);
+ SIGCHG(!(csr & USART_CSR_DCD), sig, SER_DCD, SER_DDCD);
+ SIGCHG(!(csr & USART_CSR_RI), sig, SER_RI, SER_DRI);
+ new = sig & ~SER_MASK_DELTA;
+ } while (!atomic_cmpset_32(&sc->sc_hwsig, old, new));
- uart_lock(sc->sc_hwmtx);
- csr = RD4(&sc->sc_bas, USART_CSR);
- sig = 0;
- if (csr & USART_CSR_CTS)
- sig |= SER_CTS;
- if (csr & USART_CSR_DCD)
- sig |= SER_DCD;
- if (csr & USART_CSR_DSR)
- sig |= SER_DSR;
- if (csr & USART_CSR_RI)
- sig |= SER_RI;
- new = sig & ~SER_MASK_DELTA;
- sc->sc_hwsig = new;
- uart_unlock(sc->sc_hwmtx);
return (sig);
}
OpenPOWER on IntegriCloud