This is the mail archive of the
ecos-patches@sources.redhat.com
mailing list for the eCos project.
patch: at91 serial drivers with configurable PDC
- From: Oyvind Harboe <oyvind dot harboe at zylin dot com>
- To: ecos-patches at sources dot redhat dot com
- Date: Thu, 23 Oct 2003 15:19:06 +0200
- Subject: patch: at91 serial drivers with configurable PDC
This essentially is the same patch as before, except I added an option
to enable/disable the PDC code.
The PDC code is enabled by default. The most likely reason to disable it
is extremely tight ram footprints and if the code is broken.
I have not added any other configuration options because I believe there
shouldn't be any:
- Enabling/disabling the PDC code provides the right level of
configurability.
- The "PDC buffer" isn't the serial buffer. It is more akin to a FIFO
and as such the length should be decided by interrupt latency issues.
I've set it to 128(down from 1024 bytes), though I suspect 16 bytes FIFO
is probably more than enough(if 16x5x drivers are anything to go by).
The serial buffers exist as seperately configurable options already.
- The "PDC read timeout" isn't really a timeout, but is intimately
linked with how the "PDC FIFO" works. There is a correct value, not
something to be configured. It is more akin to "FIFO threshold" value
that exists in tht 16x5x UART.
Ãyvind
Index: ecos/packages/devs/serial/arm/at91/current/ChangeLog
===================================================================
RCS file: /cvs/ecos/ecos/packages/devs/serial/arm/at91/current/ChangeLog,v
retrieving revision 1.7
diff -a -w -u -r1.7 ChangeLog
--- ecos/packages/devs/serial/arm/at91/current/ChangeLog 24 Feb 2003 14:12:18 -0000 1.7
+++ ecos/packages/devs/serial/arm/at91/current/ChangeLog 23 Oct 2003 13:02:50 -0000
@@ -1,3 +1,10 @@
+2003-10-21 Øyvind Harboe <oyvind.harboe@zylin.com>
+ * added support for hardware send/receive buffers to avoid
+ problems with real-time response in DSR routine. This helps
+ compatibility with e.g. 16x5x serial drivers that are also
+ spending time in DSR routines. This increases the amount of time
+ spent in the ISR by a relatively modest amount.
+
2003-02-24 Jonathan Larmour <jifl@eCosCentric.com>
* cdl/ser_arm_at91.cdl: Remove irrelevant doc link.
Index: ecos/packages/devs/serial/arm/at91/current/cdl/ser_arm_at91.cdl
===================================================================
RCS file: /cvs/ecos/ecos/packages/devs/serial/arm/at91/current/cdl/ser_arm_at91.cdl,v
retrieving revision 1.5
diff -a -w -u -r1.5 ser_arm_at91.cdl
--- ecos/packages/devs/serial/arm/at91/current/cdl/ser_arm_at91.cdl 24 Feb 2003 14:12:18 -0000 1.5
+++ ecos/packages/devs/serial/arm/at91/current/cdl/ser_arm_at91.cdl 23 Oct 2003 13:02:50 -0000
@@ -71,6 +71,18 @@
puts $::cdl_system_header "/***** serial driver proc output end *****/"
}
+ # The driver tries to be effective with FIFO transfers
+ implements CYGINT_IO_SERIAL_BLOCK_TRANSFER
+
+
+ cdl_option CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC {
+ display "Enable UART PDC"
+ flavor bool
+ default_value 1
+ description "Enable PDC for increased throughput and reduced interrupt latency problems. Slightly increases codesize and ram usage."
+ }
+
+
cdl_component CYGPKG_IO_SERIAL_ARM_AT91_SERIAL0 {
display "Atmel AT91 serial port 0 driver"
flavor bool
Index: ecos/packages/devs/serial/arm/at91/current/src/at91_serial.c
===================================================================
RCS file: /cvs/ecos/ecos/packages/devs/serial/arm/at91/current/src/at91_serial.c,v
retrieving revision 1.6
diff -a -w -u -r1.6 at91_serial.c
--- ecos/packages/devs/serial/arm/at91/current/src/at91_serial.c 16 Oct 2003 18:02:55 -0000 1.6
+++ ecos/packages/devs/serial/arm/at91/current/src/at91_serial.c 23 Oct 2003 13:02:50 -0000
@@ -62,15 +62,50 @@
#include <cyg/io/serial.h>
#include <cyg/infra/diag.h>
+#include <string.h>
+
#ifdef CYGPKG_IO_SERIAL_ARM_AT91
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+/* This PDC buffer size is not configurable, because this isn't really
+ the buffer for the serial port. It is more akin to a "FIFO" which
+ is fed to the serial buffers. */
+#define AT91_UART_BUF_SIZE 128
+#define RECV_UART_CHAR_TIMEOUT 50
+
+#define FLIP_BUFFER_DEF(x) \
+ struct flip_buffer { \
+ cyg_uint8 buf[2][x]; \
+ int buf_which; \
+ cyg_uint8 *buf_p; \
+ int buf_count; \
+ }
+#endif
+
#include "at91_serial.h"
+
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+FLIP_BUFFER_DEF(AT91_UART_BUF_SIZE);
+#endif
+
typedef struct at91_serial_info {
CYG_ADDRWORD base;
CYG_WORD int_num;
cyg_interrupt serial_interrupt;
cyg_handle_t serial_interrupt_handle;
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ cyg_interrupt line_interrupt;
+ cyg_handle_t line_interrupt_handle;
+ struct flip_buffer recv;
+ cyg_uint8 send_buf[2][AT91_UART_BUF_SIZE];
+ int send_buf_which;
+ int send_buf_count[2];
+ int tx_enabled;
+ cyg_uint32 stat;
+ cyg_serial_info_t config;
+ struct flip_buffer *flip;
+#endif
} at91_serial_info;
static bool at91_serial_init(struct cyg_devtab_entry *tab);
@@ -185,12 +220,31 @@
cyg_uint32 word_length = select_word_length[new_config->word_length-CYGNUM_SERIAL_WORD_LENGTH_5];
cyg_uint32 stop_bits = select_stop_bits[new_config->stop];
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ at91_chan->flip=&at91_chan->recv;
+#endif
+
if ((word_length == 0xFF) ||
(parity == 0xFF) ||
(stop_bits == 0xFF)) {
return false; // Unsupported configuration
}
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ if (init) {
+ memset (&at91_chan->config, 0, sizeof (at91_chan->config));
+ at91_chan->recv.buf_which = 0;
+ at91_chan->send_buf_which = 0;
+ at91_chan->recv.buf_count = 0;
+ at91_chan->tx_enabled = 0;
+ at91_chan->send_buf_count[0] = at91_chan->send_buf_count[1] = 0;
+ }
+
+ if (new_config->word_length != at91_chan->config.word_length ||
+ new_config->parity != at91_chan->config.parity ||
+ new_config->stop != at91_chan->config.stop ||
+ new_config->baud != at91_chan->config.baud) {
+#endif
// Reset device
HAL_WRITE_UINT32(base+AT91_US_CR, AT91_US_CR_RxRESET | AT91_US_CR_TxRESET);
@@ -200,15 +254,39 @@
// Baud rate
HAL_WRITE_UINT32(base+AT91_US_BRG, AT91_US_BAUD(select_baud[new_config->baud]));
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ // Enable RX and TX
+ HAL_WRITE_UINT32(base+AT91_US_CR, AT91_US_CR_RxENAB | AT91_US_CR_TxENAB);
+ at91_chan->config = *new_config;
+ }
+
+ if (init) {
+#endif
// Disable all interrupts
HAL_WRITE_UINT32(base+AT91_US_IDR, 0xFFFFFFFF);
+
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ // Initialize hardware buffers
+ HAL_WRITE_UINT32 (base + AT91_US_RPR, (cyg_uint32) at91_chan->recv.buf[at91_chan->recv.buf_which]);
+ HAL_WRITE_UINT32 (base + AT91_US_RCR, (cyg_uint32) AT91_UART_BUF_SIZE);
+ HAL_WRITE_UINT32 (base + AT91_US_TCR, (cyg_uint32) 0);
+ HAL_WRITE_UINT32 (base + AT91_US_TPR, (cyg_uint32) at91_chan->send_buf[at91_chan->send_buf_which]);
+
+ // Enable buffer full and timeout interrupts
+ HAL_WRITE_UINT32(base+AT91_US_IER, AT91_US_IER_TIMEOUT | AT91_US_IER_ENDRX);
+ }
+
+ // Start timeout
+ HAL_WRITE_UINT32(base+AT91_US_RTO, RECV_UART_CHAR_TIMEOUT);
+ HAL_WRITE_UINT32(base+AT91_US_CR, AT91_US_CR_STTTO);
+#else
// Enable Rx interrupts
HAL_WRITE_UINT32(base+AT91_US_IER, AT91_US_IER_RxRDY);
// Enable RX and TX
HAL_WRITE_UINT32(base+AT91_US_CR, AT91_US_CR_RxENAB | AT91_US_CR_TxENAB);
-
+#endif
if (new_config != &chan->config) {
chan->config = *new_config;
}
@@ -255,12 +333,44 @@
return ENOERR;
}
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+static void
+at91_serial_flip_send_buffer (at91_serial_info * at91_chan)
+{
+ CYG_ADDRWORD base = at91_chan->base;
+ cyg_uint32 count;
+
+// cyg_drv_isr_lock ();
+
+ HAL_READ_UINT32 (base + AT91_US_TCR, count);
+ if (count > 0) { // transmitter still busy
+ HAL_WRITE_UINT32 (base + AT91_US_IER, AT91_US_IER_ENDTX);
+ } else { // transmitter done with old buf
+// empty buffer -
+ at91_chan->send_buf_count[at91_chan->send_buf_which] = 0;
+// flip -
+ at91_chan->send_buf_which = 1 - at91_chan->send_buf_which;
+ HAL_WRITE_UINT32 (base + AT91_US_TPR, (cyg_uint32) at91_chan->send_buf[at91_chan->send_buf_which]);
+ HAL_WRITE_UINT32 (base + AT91_US_TCR,
+ (cyg_uint32) at91_chan->send_buf_count[at91_chan->send_buf_which]);
+// enable completion interrupt -
+ if (at91_chan->send_buf_count[at91_chan->send_buf_which] > 0)
+ HAL_WRITE_UINT32 (base + AT91_US_IER, AT91_US_IER_ENDTX);
+ else
+ HAL_WRITE_UINT32 (base + AT91_US_IDR, AT91_US_IER_ENDTX);
+ }
+
+// cyg_drv_isr_unlock ();
+}
+#endif
+
// Send a character to the device output buffer.
// Return 'true' if character is sent to device
static bool
at91_serial_putc(serial_channel *chan, unsigned char c)
{
at91_serial_info *at91_chan = (at91_serial_info *)chan->dev_priv;
+#ifndef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
CYG_ADDRWORD base = at91_chan->base;
cyg_uint32 stat;
@@ -274,12 +384,32 @@
} else {
return false; // Couldn't send, tx was busy
}
+#else
+ int other;
+
+ other = 1 - at91_chan->send_buf_which;
+ if (at91_chan->send_buf_count[other] >= AT91_UART_BUF_SIZE)
+ return false;
+
+// append buffer with char
+ at91_chan->send_buf[other][at91_chan->send_buf_count[other]] = (cyg_uint8) c;
+ at91_chan->send_buf_count[other]++;
+
+// flip if needed:
+ at91_serial_flip_send_buffer (at91_chan);
+
+ return true;
+#endif
}
// Fetch a character from the device input buffer, waiting if necessary
static unsigned char
at91_serial_getc(serial_channel *chan)
{
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ // we don't support this - its only for bufferless operation: see serial.c
+ return 0;
+#else
at91_serial_info *at91_chan = (at91_serial_info *)chan->dev_priv;
CYG_ADDRWORD base = at91_chan->base;
cyg_uint32 c;
@@ -287,6 +417,7 @@
// Read data
HAL_READ_UINT32(base+AT91_US_RHR, c);
return c;
+#endif
}
// Set up the device characteristics; baud rate, etc.
@@ -318,9 +449,23 @@
{
at91_serial_info *at91_chan = (at91_serial_info *)chan->dev_priv;
CYG_ADDRWORD base = at91_chan->base;
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ cyg_uint32 count;
+
+// cyg_drv_isr_lock ();
+
+ HAL_READ_UINT32 (base + AT91_US_TCR, count);
+ if (count > 0)
+ HAL_WRITE_UINT32 (base + AT91_US_IER, AT91_US_IER_ENDTX);
+ at91_chan->tx_enabled = 1;
+// cyg_drv_isr_unlock ();
+
+ (chan->callbacks->xmt_char) (chan);
+#else
(chan->callbacks->xmt_char)(chan); // Kick transmitter (if necessary)
HAL_WRITE_UINT32(base+AT91_US_IER, AT91_US_IER_TxRDY);
+#endif
}
// Disable the transmitter on the device
@@ -328,17 +473,51 @@
at91_serial_stop_xmit(serial_channel *chan)
{
at91_serial_info *at91_chan = (at91_serial_info *)chan->dev_priv;
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ at91_chan->tx_enabled = 0;
+#else
CYG_ADDRWORD base = at91_chan->base;
HAL_WRITE_UINT32(base+AT91_US_IDR, AT91_US_IER_TxRDY);
+#endif
}
// Serial I/O - low level interrupt handler (ISR)
static cyg_uint32
at91_serial_ISR(cyg_vector_t vector, cyg_addrword_t data)
{
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ serial_channel *chan = (serial_channel *) data;
+ at91_serial_info *at91_chan = (at91_serial_info *) chan->dev_priv;
+ CYG_ADDRWORD base = at91_chan->base;
+ cyg_uint32 ipr, imr, count, p;
+#endif
+ // Turn off interrupts and acknowledge the interrupt. This avoids
+ // lost interrupts and unwanted reentrancy. This problem is not entirely
+ // theoretical, since transmit and recieve interrupts are asynchronous
+ // and can happen immediately.
+
cyg_drv_interrupt_mask(vector);
cyg_drv_interrupt_acknowledge(vector);
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ HAL_READ_UINT32 (base + AT91_US_CSR, ipr);
+ HAL_READ_UINT32 (base + AT91_US_IMR, imr);
+ if ((ipr & imr & (AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT)) && at91_chan->flip) {
+ HAL_READ_UINT32 (base + AT91_US_RPR, p);
+ count = p - (cyg_uint32) at91_chan->flip->buf[at91_chan->flip->buf_which];
+ if (count > 0) { // PDC has done work
+ /* record the location of the bytes read while we were away */
+ at91_chan->flip->buf_p = at91_chan->flip->buf[at91_chan->flip->buf_which];
+ at91_chan->flip->buf_count = count;
+ /* flip the buffer */
+ at91_chan->flip->buf_which = 1 - at91_chan->flip->buf_which;
+ HAL_WRITE_UINT32 (base + AT91_US_RPR, (cyg_uint32) at91_chan->flip->buf[at91_chan->flip->buf_which]);
+ HAL_WRITE_UINT32 (base + AT91_US_RCR, (cyg_uint32) AT91_UART_BUF_SIZE);
+ }
+ HAL_WRITE_UINT32 (base + AT91_US_RTO, RECV_UART_CHAR_TIMEOUT);
+ HAL_WRITE_UINT32 (base + AT91_US_CR, AT91_US_CR_STTTO);
+ }
+#endif
return (CYG_ISR_CALL_DSR|CYG_ISR_HANDLED); // Cause DSR to be run
}
@@ -349,6 +528,67 @@
serial_channel *chan = (serial_channel *)data;
at91_serial_info *at91_chan = (at91_serial_info *)chan->dev_priv;
CYG_ADDRWORD base = at91_chan->base;
+#ifdef CYGDAT_IO_SERIAL_ARM_AT91_SERIAL_PDC
+ cyg_uint32 stat, mask;
+
+ HAL_READ_UINT32 (base + AT91_US_CSR, stat);
+ HAL_READ_UINT32 (base + AT91_US_IMR, mask);
+
+ // Check status
+ if ((mask & stat & AT91_US_IER_ENDTX)) {
+ if (at91_chan->tx_enabled) {
+ int count, other;
+ unsigned char *p;
+
+ other = 1 - at91_chan->send_buf_which;
+ if (0 && CYG_XMT_OK ==
+ (chan->callbacks->data_xmt_req) (chan, AT91_UART_BUF_SIZE - at91_chan->send_buf_count[other],
+ &count, &p)) {
+ do {
+ if (count <= 0)
+ break;
+ memcpy (at91_chan->send_buf[other] + at91_chan->send_buf_count[other], p, count);
+ at91_chan->send_buf_count[other] += count;
+
+ (chan->callbacks->data_xmt_done) (chan, count);
+ if (!(AT91_UART_BUF_SIZE - at91_chan->send_buf_count[other]))
+ break;
+ } while (CYG_RCV_OK == (chan->callbacks->data_xmt_req) (chan,
+ AT91_UART_BUF_SIZE -
+ at91_chan->send_buf_count[other],
+ &count, &p));
+ at91_serial_flip_send_buffer (at91_chan);
+ } else {
+ (chan->callbacks->xmt_char)(chan);
+ at91_serial_flip_send_buffer (at91_chan);
+ }
+ } else {
+ at91_serial_flip_send_buffer (at91_chan);
+ }
+ }
+
+ if (at91_chan->recv.buf_count > 0) { // chars waiting from ISR
+ int count;
+ unsigned char *p;
+ if (CYG_RCV_OK == (chan->callbacks->data_rcv_req) (chan, at91_chan->recv.buf_count, &count, &p)) {
+// block transfer acceptable to upper layer:
+ do {
+ memcpy (p, at91_chan->recv.buf_p, count);
+ at91_chan->recv.buf_p += count;
+ at91_chan->recv.buf_count -= count;
+ (chan->callbacks->data_rcv_done) (chan, count);
+ if (!at91_chan->recv.buf_count)
+ break;
+ } while (CYG_RCV_OK ==
+ (chan->callbacks->data_rcv_req) (chan, at91_chan->recv.buf_count, &count, &p));
+ } else {
+// block transfer not acceptable to upper layer:
+ do {
+ (chan->callbacks->rcv_char) (chan, (unsigned char) *at91_chan->recv.buf_p++);
+ } while (--at91_chan->recv.buf_count > 0);
+ }
+ }
+#else
cyg_uint32 stat, c;
// Check status
@@ -367,6 +607,7 @@
(chan->callbacks->rcv_char)(chan, c);
}
}
+#endif
cyg_drv_interrupt_unmask(vector);
}
#endif