synclink_gt: fix transmit DMA stall
authorPaul Fulghum <paulkf@microgate.com>
Tue, 31 Jul 2007 07:37:35 +0000 (00:37 -0700)
committerLinus Torvalds <torvalds@woody.linux-foundation.org>
Tue, 31 Jul 2007 22:39:37 +0000 (15:39 -0700)
Fix transmit DMA stall when write() called in window after previous
transmit DMA completes but before previous serial transmission completes.

Signed-off-by: Paul Fulghum <paulkf@microgate.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
drivers/char/synclink_gt.c

index 372a37e..bbb7f12 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: synclink_gt.c,v 4.36 2006/08/28 20:47:14 paulkf Exp $
+ * $Id: synclink_gt.c,v 4.50 2007/07/25 19:29:25 paulkf Exp $
  *
  * Device driver for Microgate SyncLink GT serial adapters.
  *
@@ -93,7 +93,7 @@
  * module identification
  */
 static char *driver_name     = "SyncLink GT";
-static char *driver_version  = "$Revision: 4.36 $";
+static char *driver_version  = "$Revision: 4.50 $";
 static char *tty_driver_name = "synclink_gt";
 static char *tty_dev_prefix  = "ttySLG";
 MODULE_LICENSE("GPL");
@@ -477,6 +477,7 @@ static void tx_set_idle(struct slgt_info *info);
 static unsigned int free_tbuf_count(struct slgt_info *info);
 static void reset_tbufs(struct slgt_info *info);
 static void tdma_reset(struct slgt_info *info);
+static void tdma_start(struct slgt_info *info);
 static void tx_load(struct slgt_info *info, const char *buf, unsigned int count);
 
 static void get_signals(struct slgt_info *info);
@@ -904,6 +905,8 @@ start:
                spin_lock_irqsave(&info->lock,flags);
                if (!info->tx_active)
                        tx_start(info);
+               else
+                       tdma_start(info);
                spin_unlock_irqrestore(&info->lock,flags);
        }
 
@@ -3871,44 +3874,58 @@ static void tx_start(struct slgt_info *info)
                        slgt_irq_on(info, IRQ_TXUNDER + IRQ_TXIDLE);
                        /* clear tx idle and underrun status bits */
                        wr_reg16(info, SSR, (unsigned short)(IRQ_TXIDLE + IRQ_TXUNDER));
-
-                       if (!(rd_reg32(info, TDCSR) & BIT0)) {
-                               /* tx DMA stopped, restart tx DMA */
-                               tdma_reset(info);
-                               /* set 1st descriptor address */
-                               wr_reg32(info, TDDAR, info->tbufs[info->tbuf_start].pdesc);
-                               switch(info->params.mode) {
-                               case MGSL_MODE_RAW:
-                               case MGSL_MODE_MONOSYNC:
-                               case MGSL_MODE_BISYNC:
-                                       wr_reg32(info, TDCSR, BIT2 + BIT0); /* IRQ + DMA enable */
-                                       break;
-                               default:
-                                       wr_reg32(info, TDCSR, BIT0); /* DMA enable */
-                               }
-                       }
-
                        if (info->params.mode == MGSL_MODE_HDLC)
                                mod_timer(&info->tx_timer, jiffies +
                                                msecs_to_jiffies(5000));
                } else {
-                       tdma_reset(info);
-                       /* set 1st descriptor address */
-                       wr_reg32(info, TDDAR, info->tbufs[info->tbuf_start].pdesc);
-
                        slgt_irq_off(info, IRQ_TXDATA);
                        slgt_irq_on(info, IRQ_TXIDLE);
                        /* clear tx idle status bit */
                        wr_reg16(info, SSR, IRQ_TXIDLE);
-
-                       /* enable tx DMA */
-                       wr_reg32(info, TDCSR, BIT0);
                }
-
+               tdma_start(info);
                info->tx_active = 1;
        }
 }
 
+/*
+ * start transmit DMA if inactive and there are unsent buffers
+ */
+static void tdma_start(struct slgt_info *info)
+{
+       unsigned int i;
+
+       if (rd_reg32(info, TDCSR) & BIT0)
+               return;
+
+       /* transmit DMA inactive, check for unsent buffers */
+       i = info->tbuf_start;
+       while (!desc_count(info->tbufs[i])) {
+               if (++i == info->tbuf_count)
+                       i = 0;
+               if (i == info->tbuf_current)
+                       return;
+       }
+       info->tbuf_start = i;
+
+       /* there are unsent buffers, start transmit DMA */
+
+       /* reset needed if previous error condition */
+       tdma_reset(info);
+
+       /* set 1st descriptor address */
+       wr_reg32(info, TDDAR, info->tbufs[info->tbuf_start].pdesc);
+       switch(info->params.mode) {
+       case MGSL_MODE_RAW:
+       case MGSL_MODE_MONOSYNC:
+       case MGSL_MODE_BISYNC:
+               wr_reg32(info, TDCSR, BIT2 + BIT0); /* IRQ + DMA enable */
+               break;
+       default:
+               wr_reg32(info, TDCSR, BIT0); /* DMA enable */
+       }
+}
+
 static void tx_stop(struct slgt_info *info)
 {
        unsigned short val;
@@ -4642,8 +4659,8 @@ static unsigned int free_tbuf_count(struct slgt_info *info)
                        i=0;
        } while (i != info->tbuf_current);
 
-       /* last buffer with zero count may be in use, assume it is */
-       if (count)
+       /* if tx DMA active, last zero count buffer is in use */
+       if (count && (rd_reg32(info, TDCSR) & BIT0))
                --count;
 
        return count;