]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/char/synclink_gt.c
synclink_gt: improve TIOCOUTQ accuracy
[linux-2.6-omap-h63xx.git] / drivers / char / synclink_gt.c
index 2c3e43bb2cc933184ea94cc6abd04a1b82c6b312..8511e2e43c1871f7f1c3cf69a40d0f288f258f89 100644 (file)
@@ -165,7 +165,7 @@ static int  read_proc(char *page, char **start, off_t off, int count,int *eof, v
 static int  chars_in_buffer(struct tty_struct *tty);
 static void throttle(struct tty_struct * tty);
 static void unthrottle(struct tty_struct * tty);
-static void set_break(struct tty_struct *tty, int break_state);
+static int set_break(struct tty_struct *tty, int break_state);
 
 /*
  * generic HDLC support and callbacks
@@ -214,6 +214,7 @@ struct slgt_desc
        char *buf;          /* virtual  address of data buffer */
        unsigned int pdesc; /* physical address of this descriptor */
        dma_addr_t buf_dma_addr;
+       unsigned short buf_count;
 };
 
 #define set_desc_buffer(a,b) (a).pbuf = cpu_to_le32((unsigned int)(b))
@@ -466,6 +467,7 @@ static void tx_start(struct slgt_info *info);
 static void tx_stop(struct slgt_info *info);
 static void tx_set_idle(struct slgt_info *info);
 static unsigned int free_tbuf_count(struct slgt_info *info);
+static unsigned int tbuf_bytes(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);
@@ -513,7 +515,7 @@ static int  wait_mgsl_event(struct slgt_info *info, int __user *mask_ptr);
 static int  tiocmget(struct tty_struct *tty, struct file *file);
 static int  tiocmset(struct tty_struct *tty, struct file *file,
                     unsigned int set, unsigned int clear);
-static void set_break(struct tty_struct *tty, int break_state);
+static int set_break(struct tty_struct *tty, int break_state);
 static int  get_interface(struct slgt_info *info, int __user *if_mode);
 static int  set_interface(struct slgt_info *info, int if_mode);
 static int  set_gpio(struct slgt_info *info, struct gpio_desc __user *gpio);
@@ -849,6 +851,7 @@ static int write(struct tty_struct *tty,
        int ret = 0;
        struct slgt_info *info = tty->driver_data;
        unsigned long flags;
+       unsigned int bufs_needed;
 
        if (sanity_check(info, tty->name, "write"))
                goto cleanup;
@@ -865,25 +868,16 @@ static int write(struct tty_struct *tty,
        if (!count)
                goto cleanup;
 
-       if (info->params.mode == MGSL_MODE_RAW ||
-           info->params.mode == MGSL_MODE_MONOSYNC ||
-           info->params.mode == MGSL_MODE_BISYNC) {
-               unsigned int bufs_needed = (count/DMABUFSIZE);
-               unsigned int bufs_free = free_tbuf_count(info);
-               if (count % DMABUFSIZE)
-                       ++bufs_needed;
-               if (bufs_needed > bufs_free)
-                       goto cleanup;
-       } else {
-               if (info->tx_active)
-                       goto cleanup;
-               if (info->tx_count) {
-                       /* send accumulated data from send_char() calls */
-                       /* as frame and wait before accepting more data. */
-                       tx_load(info, info->tx_buf, info->tx_count);
-                       goto start;
-               }
+       if (!info->tx_active && info->tx_count) {
+               /* send accumulated data from send_char() */
+               tx_load(info, info->tx_buf, info->tx_count);
+               goto start;
        }
+       bufs_needed = (count/DMABUFSIZE);
+       if (count % DMABUFSIZE)
+               ++bufs_needed;
+       if (bufs_needed > free_tbuf_count(info))
+               goto cleanup;
 
        ret = info->tx_count = count;
        tx_load(info, buf, count);
@@ -1396,10 +1390,12 @@ done:
 static int chars_in_buffer(struct tty_struct *tty)
 {
        struct slgt_info *info = tty->driver_data;
+       int count;
        if (sanity_check(info, tty->name, "chars_in_buffer"))
                return 0;
-       DBGINFO(("%s chars_in_buffer()=%d\n", info->device_name, info->tx_count));
-       return info->tx_count;
+       count = tbuf_bytes(info);
+       DBGINFO(("%s chars_in_buffer()=%d\n", info->device_name, count));
+       return count;
 }
 
 /*
@@ -1452,14 +1448,14 @@ static void unthrottle(struct tty_struct * tty)
  * set or clear transmit break condition
  * break_state -1=set break condition, 0=clear
  */
-static void set_break(struct tty_struct *tty, int break_state)
+static int set_break(struct tty_struct *tty, int break_state)
 {
        struct slgt_info *info = tty->driver_data;
        unsigned short value;
        unsigned long flags;
 
        if (sanity_check(info, tty->name, "set_break"))
-               return;
+               return -EINVAL;
        DBGINFO(("%s set_break(%d)\n", info->device_name, break_state));
 
        spin_lock_irqsave(&info->lock,flags);
@@ -1470,6 +1466,7 @@ static void set_break(struct tty_struct *tty, int break_state)
                value &= ~BIT6;
        wr_reg16(info, TCR, value);
        spin_unlock_irqrestore(&info->lock,flags);
+       return 0;
 }
 
 #if SYNCLINK_GENERIC_HDLC
@@ -3934,15 +3931,7 @@ static void tdma_start(struct slgt_info *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 */
-       }
+       wr_reg32(info, TDCSR, BIT2 + BIT0); /* IRQ + DMA enable */
 }
 
 static void tx_stop(struct slgt_info *info)
@@ -4418,6 +4407,8 @@ static void msc_set_vcr(struct slgt_info *info)
                break;
        }
 
+       if (info->if_mode & MGSL_INTERFACE_MSB_FIRST)
+               val |= BIT4;
        if (info->signals & SerialSignal_DTR)
                val |= BIT3;
        if (info->signals & SerialSignal_RTS)
@@ -4683,6 +4674,56 @@ static unsigned int free_tbuf_count(struct slgt_info *info)
        return count;
 }
 
+/*
+ * return number of bytes in unsent transmit DMA buffers
+ * and the serial controller tx FIFO
+ */
+static unsigned int tbuf_bytes(struct slgt_info *info)
+{
+       unsigned int total_count = 0;
+       unsigned int i = info->tbuf_current;
+       unsigned int reg_value;
+       unsigned int count;
+       unsigned int active_buf_count = 0;
+
+       /*
+        * Add descriptor counts for all tx DMA buffers.
+        * If count is zero (cleared by DMA controller after read),
+        * the buffer is complete or is actively being read from.
+        *
+        * Record buf_count of last buffer with zero count starting
+        * from current ring position. buf_count is mirror
+        * copy of count and is not cleared by serial controller.
+        * If DMA controller is active, that buffer is actively
+        * being read so add to total.
+        */
+       do {
+               count = desc_count(info->tbufs[i]);
+               if (count)
+                       total_count += count;
+               else if (!total_count)
+                       active_buf_count = info->tbufs[i].buf_count;
+               if (++i == info->tbuf_count)
+                       i = 0;
+       } while (i != info->tbuf_current);
+
+       /* read tx DMA status register */
+       reg_value = rd_reg32(info, TDCSR);
+
+       /* if tx DMA active, last zero count buffer is in use */
+       if (reg_value & BIT0)
+               total_count += active_buf_count;
+
+       /* add tx FIFO count = reg_value[15..8] */
+       total_count += (reg_value >> 8) & 0xff;
+
+       /* if transmitter active add one byte for shift register */
+       if (info->tx_active)
+               total_count++;
+
+       return total_count;
+}
+
 /*
  * load transmit DMA buffer(s) with data
  */
@@ -4721,6 +4762,7 @@ static void tx_load(struct slgt_info *info, const char *buf, unsigned int size)
                        set_desc_eof(*d, 0);
 
                set_desc_count(*d, count);
+               d->buf_count = count;
        }
 
        info->tbuf_current = i;