Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 1 Apr 2013 00:00:10 +0000 (UTC)
From:      Ian Lepore <ian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r248964 - head/sys/arm/at91
Message-ID:  <201304010000.r3100AEq041674@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: ian
Date: Mon Apr  1 00:00:10 2013
New Revision: 248964
URL: http://svnweb.freebsd.org/changeset/base/248964

Log:
  Enable hardware flow control and high speed bulk data transfer in at91 uarts.
  
  Changes to make rtc/cts flow control work...
  
  This does not turn on the builtin hardware flow control on the SoC's usart
  device, because that doesn't work on uart1 due to a chip erratum (they
  forgot to wire up pin PA21 to RTS0 internally).  Instead it uses the
  hardware flow control logic where the tty layer calls the driver to assert
  and de-assert the flow control lines as needed.  This prevents overruns at
  the tty layer (app doesn't read fast enough), but does nothing for overruns
  at the driver layer (interrupts not serviced fast enough).
  
  To work around the wiring problem with RTS0, the driver reassigns that pin
  as a GPIO and controls it manually.  It only does so if given permission via
  hint.uart.1.use_rts0_workaround=1, to prevent accidentally driving the pin
  if uart1 is used without flow control (because something not related to
  serial IO could be wired to that pin).
  
  In addition to the RTS0 workaround, driver changes were needed in the area
  of reading the current set of DCE signals.  A priming read is now done at
  attach() time, and the interrupt routine now sets SER_INT_SIGCHG when any
  of the DCE signals change.  Without these changes, nothing could ever be
  transmitted, because the tty layer thought CTS was de-asserted (when in fact
  we had just never read the status register, and the hwsig variable was
  init'd to CTS de-asserted).
  
  Changes to support bulk high-speed (230kbps and higher) data reception...
  
  Allow the receive fifo size to be tuned with hint.uart.<dev>.fifo_bytes.
  For high speed receive, a fifo size of 1024 works well.  The default is
  still 128 bytes if no hint is provided.  Using a value larger than 384
  requires a change in dev/uart/uart_core.c to size the intermediate
  buffer as MAX(384, 3*sc->sc_rxfifosize).
  
  Recalculate the receive timeout whenever the baud rate changes.  At low
  baud rates (19.2kbps and below) the timeout is the number of bits in 2
  characters.  At higher speed it's calculated to be 500 microseconds
  worth of bits.  The idea is to compromise between being responsive in
  interactive situations and not timing out prematurely during a brief
  pause in bulk data flow.  The old fixed timeout of 1.5 characters was
  just 32 microseconds at 460kbps.
  
  At interrupt time, check for receiver holding register overrun status
  and set the corresponding status bit in the return value.
  
  When handling a buffer overrun, get a single buffer emptied and handed
  back to the hardware as quickly as possible, then deal with the second
  buffer.  This at least minimizes data loss compared to the old logic
  that fully processed both buffers before restarting the hardware.
  
  Rewrite the logic for handling buffers after a receive timeout.  The
  original author speculated in a comment that there may be a race with
  high speed data.  There was, although it was rare.  The code now handles
  all three possible scenarios on receive timeout: two empty buffers, one
  empty and one partial buffer, or one full and one partial buffer.
  
  Reviewed by:	imp

Modified:
  head/sys/arm/at91/uart_dev_at91usart.c

Modified: head/sys/arm/at91/uart_dev_at91usart.c
==============================================================================
--- head/sys/arm/at91/uart_dev_at91usart.c	Sun Mar 31 23:24:04 2013	(r248963)
+++ head/sys/arm/at91/uart_dev_at91usart.c	Mon Apr  1 00:00:10 2013	(r248964)
@@ -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, i
 	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)
 {
+	int value;
 
-	sc->sc_txfifosz = USART_BUFFER_SIZE;
-	sc->sc_rxfifosz = USART_BUFFER_SIZE;
+	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_
 }
 
 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 
 
 	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 
 	} 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 
 		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 
 		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 *
 	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 *
 	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 *
 			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 *
 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);
 }
 



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201304010000.r3100AEq041674>