From owner-svn-src-head@FreeBSD.ORG Mon Apr 1 00:00:10 2013 Return-Path: Delivered-To: svn-src-head@freebsd.org Received: from mx1.freebsd.org (mx1.FreeBSD.org [8.8.178.115]) by hub.freebsd.org (Postfix) with ESMTP id E527518D; Mon, 1 Apr 2013 00:00:10 +0000 (UTC) (envelope-from ian@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) by mx1.freebsd.org (Postfix) with ESMTP id D52E18A9; Mon, 1 Apr 2013 00:00:10 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.6/8.14.6) with ESMTP id r3100ATO041675; Mon, 1 Apr 2013 00:00:10 GMT (envelope-from ian@svn.freebsd.org) Received: (from ian@localhost) by svn.freebsd.org (8.14.6/8.14.5/Submit) id r3100AEq041674; Mon, 1 Apr 2013 00:00:10 GMT (envelope-from ian@svn.freebsd.org) Message-Id: <201304010000.r3100AEq041674@svn.freebsd.org> From: Ian Lepore Date: Mon, 1 Apr 2013 00:00:10 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r248964 - head/sys/arm/at91 X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 01 Apr 2013 00:00:11 -0000 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..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 #include #include +#include +#include +#include #include #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); }