diff packages/devs/serial/sh/scif/current/src/sh_scif_serial.c @ 130:eb9fd8c04db3 ecos-sw-2000-10-23

Merge from eCos master repository on 2000-10-23-17:01:37-BST
author jlarmour
date Mon, 23 Oct 2000 17:10:57 +0000
parents 0c2b7be0d798
children e2d866e32dac
line wrap: on
line diff
--- a/packages/devs/serial/sh/scif/current/src/sh_scif_serial.c
+++ b/packages/devs/serial/sh/scif/current/src/sh_scif_serial.c
@@ -52,6 +52,7 @@
 #include <cyg/hal/hal_intr.h>
 #include <cyg/io/devtab.h>
 #include <cyg/infra/diag.h>
+#include <cyg/infra/cyg_ass.h>
 #include <cyg/io/serial.h>
 
 #include <cyg/hal/sh_regs.h>
@@ -134,6 +135,16 @@ typedef struct sh3_scif_info {
 
     volatile bool     tx_enabled;       // expect tx _serial_ interrupts
 
+#if (CYGINT_IO_SERIAL_SH_SCIF_DMA > 0)
+    cyg_bool          dma_enable;       // Set if DMA mode
+    cyg_uint32        dma_xmt_cr_flags; // CR flags for DMA mode
+    CYG_WORD          dma_xmt_int_num;  // DMA xmt completion interrupt
+    CYG_ADDRWORD      dma_xmt_base;     // Base address of DMA channel
+    int               dma_xmt_len;      // length transferred by DMA
+    cyg_interrupt     dma_xmt_interrupt;
+    cyg_handle_t      dma_xmt_interrupt_handle;
+    volatile cyg_bool dma_xmt_running;  // expect tx _dma_ interrupts
+#endif
 } sh3_scif_info;
 
 static bool sh3_scif_init(struct cyg_devtab_entry *tab);
@@ -157,6 +168,12 @@ static cyg_uint32 sh3_scif_er_ISR(cyg_ve
 static void       sh3_scif_er_DSR(cyg_vector_t vector, cyg_ucount32 count, 
                                    cyg_addrword_t data);
 
+#if (CYGINT_IO_SERIAL_SH_SCIF_DMA > 0)
+static cyg_uint32 sh3_dma_xmt_ISR(cyg_vector_t vector, cyg_addrword_t data);
+static void       sh3_dma_xmt_DSR(cyg_vector_t vector, cyg_ucount32 count, 
+                                  cyg_addrword_t data);
+#endif
+
 static SERIAL_FUNS(sh3_scif_funs, 
                    sh3_scif_putc, 
                    sh3_scif_getc,
@@ -282,6 +299,32 @@ sh3_scif_init(struct cyg_devtab_entry *t
         // This unmasks both interrupt sources.
         cyg_drv_interrupt_unmask(sh_chan->rx_int_num);
     }
+
+#if (CYGINT_IO_SERIAL_SH_SCIF_DMA > 0)
+    // Assign DMA channel and interrupt if requested
+    if (sh_chan->dma_enable) {
+        // FIXME: Need a cleaner way to assign DMA channels
+        static int dma_channel = 0;
+        sh_chan->dma_xmt_int_num = dma_channel+CYGNUM_HAL_INTERRUPT_DMAC_DEI0;
+        sh_chan->dma_xmt_base = (dma_channel*0x10)+CYGARC_REG_SAR0;
+        dma_channel++;
+
+        // Enable the DMA engines.
+        HAL_WRITE_UINT16(CYGARC_REG_DMAOR, CYGARC_REG_DMAOR_DME);
+
+        cyg_drv_interrupt_create(sh_chan->dma_xmt_int_num,
+                                 3,
+                                 (cyg_addrword_t)chan, // Data item passed to interrupt handler
+                                 sh3_dma_xmt_ISR,
+                                 sh3_dma_xmt_DSR,
+                                 &sh_chan->dma_xmt_interrupt_handle,
+                                 &sh_chan->dma_xmt_interrupt);
+        cyg_drv_interrupt_attach(sh_chan->dma_xmt_interrupt_handle);
+        cyg_drv_interrupt_unmask(sh_chan->dma_xmt_int_num);
+    }
+    sh_chan->dma_xmt_running = false;
+#endif // CYGINT_IO_SERIAL_SH_SCIF_DMA
+
     sh3_scif_config_port(chan, &chan->config, true);
     return true;
 }
@@ -365,33 +408,49 @@ sh3_scif_set_config(serial_channel *chan
 #ifdef CYGOPT_IO_SERIAL_FLOW_CONTROL_HW
     case CYG_IO_SET_CONFIG_SERIAL_HW_RX_FLOW_THROTTLE:
       {
-          cyg_uint8 _scfcr;
+          // Control RX flow control by disabling/enabling RX interrupt.
+          // When disabled, FIFO will fill up and clear RTS.
+          cyg_uint8 _scscr, mask = 0;
           sh3_scif_info *ser_chan = (sh3_scif_info *)chan->dev_priv;
           cyg_addrword_t base = ser_chan->ctrl_base;
           cyg_uint8 *f = (cyg_uint8 *)xbuf;
-          unsigned char mask=0;
           if ( *len < *f )
               return -EINVAL;
           
           if ( chan->config.flags & CYGNUM_SERIAL_FLOW_RTSCTS_RX )
-              mask |= CYGARC_REG_SCFCR1_MCE;
-          HAL_READ_UINT8(base+SCIF_SCFCR, _scfcr);
+              mask |= CYGARC_REG_SCSCR2_RIE;
+          HAL_READ_UINT8(base+SCIF_SCSCR, _scscr);
           if (*f) // we should throttle
-              _scfcr &= ~mask;
+              _scscr &= ~mask;
           else // we should no longer throttle
-              _scfcr |= mask;
-          HAL_WRITE_UINT8(base+SCIF_SCFCR, _scfcr);
+              _scscr |= mask;
+          HAL_WRITE_UINT8(base+SCIF_SCSCR, _scscr);
       }
       break;
     case CYG_IO_SET_CONFIG_SERIAL_HW_FLOW_CONFIG:
       {
-        // Clear DSR/DTR flag as it's not supported.
-        if (chan->config.flags &
-            (CYGNUM_SERIAL_FLOW_DSRDTR_RX|CYGNUM_SERIAL_FLOW_DSRDTR_TX)) {
-            chan->config.flags &= ~(CYGNUM_SERIAL_FLOW_DSRDTR_RX|
-                                    CYGNUM_SERIAL_FLOW_DSRDTR_TX);
-            return -EINVAL;
-        }
+          // Handle CTS/RTS flag
+          if ( chan->config.flags & 
+               (CYGNUM_SERIAL_FLOW_RTSCTS_RX | CYGNUM_SERIAL_FLOW_RTSCTS_TX )){
+              cyg_uint8 _scfcr;
+              sh3_scif_info *ser_chan = (sh3_scif_info *)chan->dev_priv;
+              cyg_addrword_t base = ser_chan->ctrl_base;
+              cyg_uint8 *f = (cyg_uint8 *)xbuf;
+
+              HAL_READ_UINT8(base+SCIF_SCFCR, _scfcr);
+              if (*f) // enable RTS/CTS flow control
+                  _scfcr |= CYGARC_REG_SCFCR1_MCE;
+              else // disable RTS/CTS flow control
+                  _scfcr &= ~CYGARC_REG_SCFCR1_MCE;
+              HAL_WRITE_UINT8(base+SCIF_SCFCR, _scfcr);
+          }
+          // Clear DSR/DTR flag as it's not supported.
+          if (chan->config.flags &
+              (CYGNUM_SERIAL_FLOW_DSRDTR_RX|CYGNUM_SERIAL_FLOW_DSRDTR_TX)) {
+              chan->config.flags &= ~(CYGNUM_SERIAL_FLOW_DSRDTR_RX|
+                                      CYGNUM_SERIAL_FLOW_DSRDTR_TX);
+              return -EINVAL;
+          }
       }
       break;
 #endif
@@ -402,6 +461,130 @@ sh3_scif_set_config(serial_channel *chan
 }
 
 
+#if (CYGINT_IO_SERIAL_SH_SCIF_DMA > 0)
+
+// Must be called with serial interrupts disabled
+static xmt_req_reply_t
+sh3_scif_start_dma_xmt(serial_channel *chan)
+{
+    int chars_avail;
+    unsigned char* chars;
+    xmt_req_reply_t res;
+
+    // We can transfer the full buffer - ask how much to transfer
+    res = (chan->callbacks->data_xmt_req)(chan, chan->out_cbuf.len, 
+                                          &chars_avail, &chars);
+    if (CYG_XMT_OK == res) {
+        sh3_scif_info *sh_chan = (sh3_scif_info *)chan->dev_priv;
+        cyg_uint32 dma_base = sh_chan->dma_xmt_base;
+        cyg_uint32 scr;
+
+        // Save the length so it can be used in the DMA DSR
+        sh_chan->dma_xmt_len = chars_avail;
+
+        // Flush cache for the area
+        HAL_DCACHE_FLUSH((cyg_haladdress)chars, chars_avail);
+
+        // Program DMA
+        HAL_WRITE_UINT32(dma_base+CYGARC_REG_CHCR, 0); // disable and clear
+        HAL_WRITE_UINT32(dma_base+CYGARC_REG_SAR, (cyg_uint32)chars);
+        HAL_WRITE_UINT32(dma_base+CYGARC_REG_DAR, 
+                         (sh_chan->ctrl_base+SCIF_SCFTDR) & 0x0fffffff);
+        HAL_WRITE_UINT32(dma_base+CYGARC_REG_DMATCR, chars_avail);
+        // Source increments, dest static, byte transfer, enable
+        // interrupt on completion.
+        HAL_WRITE_UINT32(dma_base+CYGARC_REG_CHCR,
+                         sh_chan->dma_xmt_cr_flags | CYGARC_REG_CHCR_SM0 \
+                         | CYGARC_REG_CHCR_IE | CYGARC_REG_CHCR_DE);
+
+        // Enable serial interrupts
+        HAL_READ_UINT8(sh_chan->ctrl_base+SCIF_SCSCR, scr);
+        scr |= CYGARC_REG_SCSCR2_TIE;
+        HAL_WRITE_UINT8(sh_chan->ctrl_base+SCIF_SCSCR, scr);
+    }
+
+    return res;
+}
+
+// must be called with serial interrupts masked
+static void
+sh3_scif_stop_dma_xmt(serial_channel *chan)
+{
+    sh3_scif_info *sh_chan = (sh3_scif_info *)chan->dev_priv;
+    cyg_uint32 dma_base = sh_chan->dma_xmt_base;
+    cyg_uint32 cr;
+
+    // Disable DMA engine and interrupt enable flag.  Should be safe
+    // to do since it's triggered by the serial interrupt which has
+    // already been disabled.
+    HAL_READ_UINT32(dma_base+CYGARC_REG_CHCR, cr);
+    cr &= ~(CYGARC_REG_CHCR_IE | CYGARC_REG_CHCR_DE);
+    HAL_WRITE_UINT32(dma_base+CYGARC_REG_CHCR, cr);
+
+    // Did transfer complete?
+    HAL_READ_UINT32(dma_base+CYGARC_REG_CHCR, cr);
+    if (0 == (cr & CYGARC_REG_CHCR_TE)) {
+        // Transfer incomplete. Report actually transferred amount of data
+        // back to the serial driver.
+        int chars_left;
+        HAL_READ_UINT32(dma_base+CYGARC_REG_DMATCR, chars_left);
+        CYG_ASSERT(chars_left > 0, "DMA incomplete, but no data left");
+        CYG_ASSERT(chars_left <= sh_chan->dma_xmt_len,
+                   "More data remaining than was attempted transferred");
+
+        (chan->callbacks->data_xmt_done)(chan, 
+                                         sh_chan->dma_xmt_len - chars_left);
+    }
+
+#ifdef CYGDBG_USE_ASSERTS
+    {
+        cyg_uint32 dmaor;
+        HAL_READ_UINT32(CYGARC_REG_DMAOR, dmaor);
+        CYG_ASSERT(0== (dmaor & (CYGARC_REG_DMAOR_AE | CYGARC_REG_DMAOR_NMIF)),
+                   "DMA error");
+    }
+#endif
+    
+    // The DMA engine is free again.
+    sh_chan->dma_xmt_running = false;
+}
+
+// Serial xmt DMA completion interrupt handler (ISR)
+static cyg_uint32 
+sh3_dma_xmt_ISR(cyg_vector_t vector, cyg_addrword_t data)
+{
+    serial_channel *chan = (serial_channel *)data;
+    sh3_scif_info *sh_chan = (sh3_scif_info *)chan->dev_priv;
+    cyg_uint32 _cr;
+
+    // mask serial interrupt
+    HAL_READ_UINT8(sh_chan->ctrl_base+SCIF_SCSCR, _cr);
+    _cr &= ~CYGARC_REG_SCSCR2_TIE;      // Disable xmit interrupt
+    HAL_WRITE_UINT8(sh_chan->ctrl_base+SCIF_SCSCR, _cr);
+
+    // mask DMA interrupt and disable engine
+    HAL_READ_UINT32(sh_chan->dma_xmt_base+CYGARC_REG_CHCR, _cr);
+    _cr &= ~(CYGARC_REG_CHCR_IE | CYGARC_REG_CHCR_DE);
+    HAL_WRITE_UINT32(sh_chan->dma_xmt_base+CYGARC_REG_CHCR, _cr);
+
+    return CYG_ISR_CALL_DSR;  // Cause DSR to be run
+}
+
+// Serial xmt DMA completion interrupt handler (DSR)
+static void       
+sh3_dma_xmt_DSR(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
+{
+    serial_channel *chan = (serial_channel *)data;
+    sh3_scif_info *sh_chan = (sh3_scif_info *)chan->dev_priv;
+
+    (chan->callbacks->data_xmt_done)(chan, sh_chan->dma_xmt_len);
+
+    // Try to load the engine again.
+    sh_chan->dma_xmt_running = 
+        (CYG_XMT_OK == sh3_scif_start_dma_xmt(chan)) ? true : false;
+}
+#endif // CYGINT_IO_SERIAL_SH_SCIF_DMA
+
 
 // Enable the transmitter on the device
 static void
@@ -414,6 +597,24 @@ sh3_scif_start_xmit(serial_channel *chan
     if (sh_chan->tx_enabled)
         return;
 
+#if (CYGINT_IO_SERIAL_SH_SCIF_DMA > 0)
+    // Check if the engine is already running. If so, return. Note
+    // that there will never be a race on this flag - the caller of
+    // this function is respecting a per-channel lock.
+    if (sh_chan->dma_xmt_running)
+        return;
+    // If the channel uses DMA, try to start a DMA job for this -
+    // but handle the case where the job doesn't start by falling
+    // back to the FIFO/interrupt based code.
+    if (sh_chan->dma_enable) {
+        _block_status = sh3_scif_start_dma_xmt(chan);
+        CYG_ASSERT(_block_status != CYG_XMT_EMPTY, 
+                   "start_xmit called with empty buffers!");
+        sh_chan->dma_xmt_running = 
+            (CYG_XMT_OK == _block_status) ? true : false;
+    }
+#endif // CYGINT_IO_SERIAL_SH_SCIF_DMA
+
     if (CYG_XMT_DISABLED == _block_status) {
         // Mask interrupts while changing the CR since a rx
         // interrupt or another thread doing the same in the
@@ -447,6 +648,12 @@ sh3_scif_stop_xmit(serial_channel *chan)
     }
     cyg_drv_isr_unlock();
 
+#if (CYGINT_IO_SERIAL_SH_SCIF_DMA > 0)
+    // If the channel uses DMA, stop the DMA engine.
+    if (sh_chan->dma_xmt_running)
+        sh3_scif_stop_dma_xmt(chan);
+    else // dangling else!
+#endif // CYGINT_IO_SERIAL_SH_SCIF_DMA
         sh_chan->tx_enabled = false;
 }