This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

UART2 problems



What is the proper way to use UART with interrupts on DM355?

Here is how I have implemented UART2:

interrupt()
{
  // first, check if we need to transmit anything
  if ( LSR & THRE) // TX fifo empty
  {
      copy_16_bytes_from_buffer_to_THR;
  }
  else
  {
      disable_THR_EMPTY_interrupts_but_keep_all_other_enabled;
  }
  // now, let's check if we received anything
  while ( LSR & DR) //  RX fifo has data
  {
      copy_byte_from_RBR_to_buffer;
  }


transmit_bytes_function() // function is called when bytes need to be transmitted
{
   copy_data_to_buffer; // copy data to circular buffer
   if (THR_EMPTY_interrupt_disabled)
   {
      enable_THR_EMPTY_interrupt_and_all_others;
   }
}

During intensive communication the driver gets into a state where THR FIFO interrupt is enabled, the THR FIFO is empty but the interrupt does not fire.

What could be the problem?

  • Without a view into how your interrupt registers are configured, I can only assume the problem is that maybe you've registered for more interrupt notifications other than just

    0 Received data available
    1 Transmitter holding register empty

    Or perhaps you receiving data constantly and therefore while ( LSR & DR) condition hold true for a long time?

    Marcus

     

  • My problem started when I used default driver for UART2. External device would deassert CTS. After that, DM355 would go to deep sleep mode. While sleeping, external device would assert CTS and start a wakeup sequence on DM355, after wakeing up, DM355 would try to send the data but the data would not come out. Only after external device deasserts CTS and asserts it back, the data would come out. I tried figuring out how the default driver worked so I could fix the bugs, but the driver code was so convoluted (dute to support a variety of 8250/16550/16450 devices) that I gave up. The driver would even try to write to registers that are not defined in the datasheet (offset 0x18, 0x1C), set bits in registers that according to the datasheet must be set to 0 (Ex: bit 3 in IER). So, I gave up on that driver and decided to write my own.

    Here is my interrupt code:

    static int uart355_interrupt(struct uart355 *u)
    {
      int work_done = 0;
      int cnt = 0;
      int status;
     
      status = u->regs->LSR;
      while (u->regs->LSR & LSR_DR)
      {
         if (u->rxtail != ((u->rxhead + 1) & (RX_BUFFER_SIZE - 1)))
         {
            u->rxbuf[u->rxhead] = u->regs->RBR;
            u->rxhead = (u->rxhead + 1) & (RX_BUFFER_SIZE - 1);
         }
         else
         {
            cnt = u->regs->RBR; // read character from the buffer and discard it...
         }
         cnt = 1;
      //work_done = 1;
      }
     
      if (cnt)
         wake_up_interruptible(&u->wq); // wake up anyone waiting for data...
     
      if (status & LSR_THRE)
      {
         if (u->txhead != u->txtail)
         {
           cnt = 0;
           do
           {
             u->regs->THR = u->txbuf[u->txtail];
             u->txtail = (u->txtail + 1) & (TX_BUFFER_SIZE - 1);
             cnt++;
           }
           while (u->txhead != u->txtail && cnt < 16);
           if (u->txhead == u->txtail)
           {
              // TX buffer is empty now!!!
              u->regs->IER = IER_ELSI | IER_ERBI;
           }
           else
              work_done = 1;
        }
        else
          // TX buffer is empty!
          u->regs->IER = IER_ELSI | IER_ERBI;
      }
      else
         work_done = 1;

      return work_done;
    }

    Here is my write to UART function:

    static ssize_t dev_write(struct file *filp, const char *buff, size_t len, loff_t *off)
    {
      struct uart355 *u = filp->private_data;
      int t;
      int la = 0, l1;
      unsigned long flags;
     
      if (len == 0)
        return 0;
     
      // Writing at the head
      while (len)
      {
        t = u->txtail; // this is where the data is picked up from the buffer for Xmission.
        if (t == ((u->txhead + 1) & (TX_BUFFER_SIZE - 1)))
        {
          printk("\nUART2 full!\n");
          break; // tail is right in front of head - no mode space in the buffer
        }
     
        if (t > u->txhead)
          l1 = t - u->txhead - 1;
        else
          l1 = TX_BUFFER_SIZE - u->txhead;
     
        if (l1 > len)
          l1 = len;
        //printk("\nU355: %d chars @ %d, %04X\n", l1, u->txhead, u->regs->PID1);
        if (copy_from_user(&(u->txbuf[u->txhead]), buff, l1))
        {
          printk("copy failed %d\n", t);
          return -ENOMEM;
        }
     
        u->txhead = (u->txhead + l1) & (TX_BUFFER_SIZE - 1);
        la += l1;
        len -= l1;
      }
      t = 0;
      if (la)
      {
        // TODO: Enable an interrupt here!
        // u->regs->IER = IER_ETBEI | IER_ELSI | IER_ERBI;  // enable all interrupts...
     
        t = 1;
        spin_lock_irqsave(&u->lock, flags); // disable interrupts
        if (!(u->regs->IER & IER_ETBEI))
        {
          // Two things could have happened here - interrupt already ran and the buffer is empty
          // or interrupt was disabled and we need to restart the transfer. For the former
          // case, enabling interrupt will result in an empty interrupt where it gets fired 
          // but does nothing and gets disabled.
     
          u->regs->IER = IER_ETBEI | IER_ELSI | IER_ERBI; // enable all interrupts...
          // Check to see if the FIFO is empty
          t = 2;
     
          if (u->regs->IIR & IIR_NOINTS) // no int?
          {
            // FIFO is empty - enabling an interrupt simply would not cut it.
            // I will have to load one character into the buffer first...
            // IRQs are spin-locked (will not fire) so we can safely "mess" with the 
            // tail pointers.
     
            // We will read one character and put it in the buffer
            u->regs->THR = u->txbuf[u->txtail];
            u->txtail = (u->txtail + 1) & (TX_BUFFER_SIZE - 1);
            t = 3;
          }
     
        }
        spin_unlock_irqrestore(&u->lock, flags); // reenable interrupts
      }
      
      return la;
    }

  • This is how I setup the UART registers:

    static int uart355_config(struct uart355 * u, int baud)
    {
      // Clock input is set to 67.5MHz
      unsigned long flags;
      int tmp;

      spin_lock_irqsave(&u->lock, flags); // disable interrupts
     
      u->regs->IER = 0; // disable all interrupts
      u->regs->FCR = 0;
      u->regs->PWREMU_MGMT = 0; // Put UART in reset
      mdelay(10);
     
      uart355_setbr(u, baud); // set DLH/DLL
     
      u->regs->FCR = FCR_FIFOEN;
      // This command will also clear all FIFOs
      u->regs->FCR = FCR_RXFIFL_8 | FCR_DMAMODE1 | FCR_TXCLR | FCR_RXCLR | FCR_FIFOEN;
      u->regs->LCR = LCR_WLS8;
      u->regs->MCR = MCR_AFE_RTSCTS;
      u->regs->IER = IER_ELSI | IER_ERBI;
      tmp = u->regs->LSR; // need to read the status register to clear interrupts...
      u->regs->PWREMU_MGMT = (3 << 13) | (1);
     
      spin_unlock_irqrestore(&u->lock, flags); // enable interrupts (if were enabled)
      return 0;
    }

    Update:

    My main question is "How does Ti recommend using this UART with interrupts"? What is the scheme? 

    My interrupt would copy data from circular buffer into TX FIFO. If no more data is present, TX FIFO empty interrupt would get disabled. At the same time, my write() function would put data into circular buffer and enable TX FIFO empty interrupt (this should automatically fire an interrupt if FIFO is empty, right?). This is where I encounter a problem. My TX FIFO empty interrupt at some point stops firing with ETBIE interrupt enabled, THRE in LSR set. This causes my circular buffer overflow.

    As for RX data, the interrupt would copy data from RBR to another circular buffer. read() function would read that data from that buffer.

  • Some general comments. Not an expert on this specific UART. Reading the IIR may clear the IRQ. Try replacing

    if (u->regs->IIR & IIR_NOINTS) // no int?

    with

    if (u->regs->LSR & LSR_THRE) // no int?

    The Tx IRQ code uses a saved copy of the LSR. Slight possibility that Tx FIFO empties while you are emptying the Rx FIFO.Should be okay I guess. The Tx FIFO empty interrupt should persist and call immediately your ISR again. Linux will count the spurious IRQs where the handler returns 0. At something like 10000 spurious IRQs, it's automatically shut downs the IRQ.

    You might need to be careful about reading the LSR. On some UART implementations, reading the LSR will clear error flags. You might need to keep a shadow copy around so that you inspect for and handle errors.

  • Norman,

    Actually, in this implementation reading LSR will clear receiver line status interrupts. I did not see anything about reading IIR would clear interrupts... but who says you should trust the datasheet?. You might be onto something though...

    As far as shutting down IRQ, you normally would see a message on the terminal.

    BTW, I cannot figure out the purpose of IIR at all. All of that information plus more could be obtained by reading LST. Unless I have to read it to clear the interrupts. 

  • It's been a while since I had to write 8250/16550 UART code. Vaguely remember I needed the IIR to implement HW flow control. Also some FPGA implementations required the IIR to be read in the ISR in order to receive more IRQs. The Linux 8250 driver is deservedly complicated becaused it has to cover all the different implementations.

  • DM355 UART documentation does not mention anything about flow control in the description of IIR. I still would like to know how TI recommends running their UART from interrupts.

  • I think the IIR is the only way to get notification that the Rx FIFO is getting nearly full or past a threshold. The ISR then deasserts the CTS to tell the other end to stop transmitting before this end's Rx FIFO overflows. TI's responsiveness is a bit spotty with Linux probems. Especially if there is an existing Linux driver.

  • If you look at UART documentation for DM355, IIR has three fields:

    -FIFOEN - flag indicating if FIFOs are enabled

    - INTID - Interrupt type. The following types are supported: Transmit holding reg empty, RX data available, RX line status, character timeout in RX.

    - IPEND - indicates if an interrupt is pending.

    No other fields or anything. Not sure how this can help with flow control.

    My question is not about LINUX driver. Forget Linux. How would one use UART... you know my question.

    Even more, UART in Linux is buggy when it is used with DM355. The flow control does not work right.

    1. When external device deasserts CTS, a whole bunch of characters may still come out on UART (I think up to 16). Thank goodness my device was able to handle that.

    2. If I write into the driver a bunch of characters and deassert CTS line, UART will send first 17 bytes, "eat" next 15, and send the rest out normally. If I send data bytes 1,2,3,..., 49, 50, it would send 1, 2, 3, 4, ... ,15, 16, 17, 33, 34, 35, ..., 49, 50. It would drop bytes 18 through 32. It was because the driver would write 16 bytes in FIFO (of which first would be shifted in to TX shift register, making one more spot in the FIFO) and when write another 16 bytes without checking to see if the FIFO is empty. I was able to fix this bug by adding an extra check in transmit_chars() function in 8250.c file.

    3. When going to DEEPSLEEP with CTS deasserted and waking up with CTS asserted, the driver would not send anything out until CTS line is toggled once more. I could not fix that bug. I don't know how the driver works with flow control. It does not look like it is using GPIOs (too architecture specific) and I don't see how it can determine the status of CTS line (input) using any of the status registers because there are none with that information according to the datasheet (MSR is not present in DM355, even though it is used by 8250.c).

    The last issue made me write my own driver. I am struggling to determine how the thing works. I know it does not work like a standard 16550 device (since standard driver cannot work with it properly) event though it looks and smells like one.

  • I've implemented flow control using INTID=2=RDAINT. If I get one of those it means my Rx FIFO is filled past the trigger level, I deassert the CTS or RTS. Never could remember which end I am, DTE or DCE. Maybe I am using it incorrectly. Yeah, Linux development is brutal. Managers think its free.

  • Gennadiy,

    Our current Linux UART driver is not using flow control which is what you require.

    I'm attempting to locate an example with flow control enabled.

     

  • UART has automatic flow control in hardware. To have the flow control enabled, I just need to write 0x22 into MCR. This activates the flow control (RTS and CTS) in hardware. It seems to work just as documented, i.e. RTS is deasserted RX FIFO gets filled to the trigger level. At the same time, the RX interrupt is generated. CTS also appears to work without my intervention.

    There is one downside to the RTS (output) signal in hardware flow control. It appear to use the same trigger level as the interrupt. This causes RTS to be asserted every 8 bytes. This deassertion normally would not be needed since DM355 is plenty capable of processing a constant stream of data on UART. In ideal case, it would be nice to be able to set RTS trigger level higher than than RX FIFO interrupt level. However, I don't think it is available in this implementation of UART.

    An example of using UART with interrupts would be good.

    Thank you.

  • Gennadiy,

    I don't believe it's necessary to disable the IER.ETBEI (THR Interrtup) since it the THR empty interrupt will only fire upon empty fifo.  The interrupt condition will clear upon writing to theTHR  Register.  There could be some race condition when continuously enabling/disabling the IER.ETBEI interrupt.

    Marcus

  • THR interrupt gets disabled when there is nothing else to transmit. Only when more data is added to the buffer, we need to start firing the interrupts and only at that point the interrupt gets enabled. If I don't disable THR interrupt upon completing data transfer, it will keep firing using all the CPU time because THR FIFO will remain empty.

     

  • I reviewed a C5x based UART driver that's using flow control and it wrote to the fifo before re-enabling the interrupt. Also it used the IIR in the ISR routine for determining the interrupt condition.  I'm not sure if this make a difference, but could you use the attached as a guideline.  Look particularly at

     

    pspdrive0312.UART.crs_01_30_01_C5x.zip\pspdrivers_01_30_01_C5x\packages\ti\pspiom\uart\src\UART.c

     

    4035.cslr_uart.h

  • Particularly look at the uartIsr routine

  • I think, I got my UART2 character driver working. I decided to disable the line status interrupt (as it was mentioned above) and that improved few things. I also removed writing data to the FIFO from my write() function. In that function I would just enable the interrupt after copying the data to a circular buffer. Inside the interrupt, I read RX FIFO (until it is empty) and if TX FIFO is empty, write next 16 characters to be xmitted. After that if there are no more bytes to transmit, I disable the TX interrupt.

    Not sure why disabling line status interrupt would help. I was reading LSR inside the interrupt so the interrupt flag should get cleared. I think there is something special about that interrupt that is not documented. Something about the way it needs to be handled.

  •