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.

How do I fix higher speed baud rates on DM6467?

Other Parts Discussed in Thread: MAX3243

My DM6467 runs the console at 115,200 baud.  Using "stty <baudrate>", I find that when I try higher speeds (230,400; 460,800; 921,600) that they are more than 5% off in speed and don't work.

Looking at spruer6d.pdf table 6, baud rates up to 115,200 require a 16x configuration, while higher baud rates require a 13x configuration.  I strongly suspect that the source code fails to change to 13x for the higher speeds.  In fact, when I search the kernel source in folder arch/arm/mach-davinci, I find no references to "16x" or "MODE_SELECT" or even "MDR1" (ref pdf table 45).  

I've searched through the ratsnest of code, and can't seem to find where the baud rate is being set.

Any help would be greatly appreciate.

(((This was originally posted on a different forum and then moved.  But since moved posts seem to never get answered, I'm re-posting this now in hopes that it will get an answer.  Thanks very much for your friendly consideration in this matter.)))

  • Try looking in drivers/serial/omap-serial.c. Looks like it's doing the 13x thing.

  • Hi,

    There is MAX3243 transceiver before UART0 db9 connector, which supports upto 250Kbits/s. That could be restricting it.

    Regards,

    Prakash

  • Norman,  

    Thanks.  Funny, though, my copy of the kernel source doesn't have an omap-serial.c file at all!  How do I identify my version?  Perhaps by boot output "Linux-2.6.32".

    And even if I had the omap file, I'm on DaVinci dm6467.  Isn't that different?  I do understand I could get search ideas from the omap-serial.c file.  I do find a copy of source at http://lxr.free-electrons.com/source/drivers/tty/serial/omap-serial.c .  I'll extract search terms out of this source, to try and find similar terms in my source...

    I've searched all of the source for MDR1 and DLH, to no avail.  This simply can't be!  Somewhere, somehow, the processor baud rate registers must be getting set.  And it can't be 100% boot-up default, because stty does cause the speed to change.  I attempted to drill down through the stty source code, but got nowhere...

    I plan on doing more code searching today.  Please let me know if you think of something else...

    Prakash,

    Thanks for the suggestion, but that can't be the problem.  Aside from my customized board having a different RS232 driver, the signals ARE getting through; they're just at the wrong baud rate.  They're off by 7% to 10% at various speeds, all the way up to 921,600.  The waveforms look nice at the highest speed.  They're just the wrong baud rate.  So this must be coming out of the dm6467.

  • ...correction.  I do find MDR1 in the file cir_hw.h.  However, the only .c use of cir_hw.c is for CIR, and doesn't access the DHL also defined in cir_hw.c.  So that was a dead end.

    Meanwhile, in arch/arm/mach-davinci/serial.c I ought to find success.  However, the stuff in that file makes little sense to me.  Function davinci_serial_init() has only a couple dozen lines, but I can't figure out even in it how the baud rate is being set.  It goes into a clk_get() and clk_get_rate() rats nest that I've searched for other reasons in the past.  I wish I could find the word "set" in there somewhere!  Searching again...

  • drivers/tty/serial/8250/8250.c is the serial driver for dm6467 and serial8250_do_set_termios is the function which will get called on doing stty for baudrate change. Currently doesnot modify MDR1 register, so it always operates in 16x mode.

    Regards,

    Prakash

  • Prakash,

    Thanks.  I sincerely appreciate your help.  This is EXACTLY the type of info I'm looking for.  In detail, however, my source doesn't match what you're saying, so perhaps you can help me with a little translation.

    1) I'm using Arago Linux-2.6.32

    2) There is no drivers/tty folder at all, much less containing below it a 8250.c.  I do, however, find a drivers/serial/8250.c.  However, it has no serial8250_do_set_termios function in it.  I'm currently searching all the source for "serial8250_do_set_termios", but I don't have high hopes. ...nope, none found.

    3) I have found various set_termios functions elsewhere, such as in generic_serial.c.  However, I don't know if this is being linked into my kernel image (uImage) or not.  Perhaps you can advise me as to how to figure out WHICH files are being linked in.  I don't see a link output map or anything like that, and don't know how to cause one to be generated.  If I could, I might be able to figure out which function does the job for me.

    4) Otherwise, perhaps you know or can advise me on how to find the correct set_termios function by searching from the top down...  I realize now I recently did search all the source for "set_termios".  I found 145 files, but none of them jumps out at me.  None of them have "8250" in the filename.

    5) Anticipating the final destination, none of what I've found, including generic_serial.c, seems to modify processor registers.  This must be done, and unless it's being done extremely indirectly, I should have been able to find already where it's being done.  The "gs_set_termios()" in generic_serial.c 

    ...I just can't seem to get the dots connected enough to get me to the place I'll then want to upgrade to support 13x...

  • ...is drivers/serial/8250.c being linked into my kernel and is it the one being used in my case?  I do track down through it and find "serial8250_set_termios" (not "serial8250_do_set_termious").  This in turn calls "serial8250_get_divisor" which calls "uart_get_divisor" which exists in serial_core.c and has code "quot = (port->uartclk + (8 * baud)) / (16 * baud);" that with that "16" in there looks like hard coding of 16x.

    I don't see later where that goes into an actual processor hardware register.  Nevertheless, if this IS the correct source, I think I can edit it as I need.

    But is it the correct source?  I may just have to put a unique printk in it and install it, then see if the unique printk comes out...

    Otherwise, if you can get me to the other end of the question quicker, thanks!

  • ...yep, I modified serial8250_init() and my modification printed in the console boot log.  

    I also modified serial8250_get_divisor() to output baud and quot (divisor).  I see it coming out.  And when I run "stty 57600" I see an appropriately different message come out (at the old baud rate, so I see it before losing rate).

    And most importantly, when I run "stty 921600", I get my message saying "Serial: serial8250_get_divisor, baud=921600, quot=2" and that for sure is WRONG as I expected.  With 24MHz crystal, 24000000/16/2 is 750000.  And indeed this is the speed I got on the oscilloscope earlier when I tried 921,600 baud.

    SO NOW I know one place to intercept.  But do I hard code setting of 13x vs 16x here?  It seems like the wrong place.  I need to track down where this info gets used, and where the darn registers are really set, and do the 13x vs 16x thing THERE instead.  If I have access to baud variable, I can bound it and make the decision, slightly hard coded.  Or I could overload a bit on the quot var, as I see done elsewhere in serial8250_get_divisor() (for SMSC SuperIO chips).

    Any suggestions, please?

  • Yes, the driver and function i gave reference are based linus's kernel.

    I checked on your kernel, the driver and funtion you are mentioning are correct. You can go ahead and modify it. As i had mentioned earlier current driver doesnot take care of configuring it for 13x, 16x mode is default and is used all along.

    Thanks,

    Prakash

  • You should be configuring mdr1 in serial8250_do_set_termious function, omap-serial.c driver is also does it from termios from function

    Here is code snippet from omap-serial.c.

            if (baud > 230400 && baud != 3000000)
                    up->mdr1 = UART_OMAP_MDR1_13X_MODE;
            else
                    up->mdr1 = UART_OMAP_MDR1_16X_MODE;

            if (up->errata & UART_ERRATA_i202_MDR1_ACCESS)
                    serial_omap_mdr1_errataset(up, up->mdr1);
            else
                    serial_out(up, UART_OMAP_MDR1, up->mdr1);

  • Oops. I didn't know that the DM6467 Linux is off on it's own branch.I think you are on the right track. I think you could port some of the omap-serial.c code from the other branch into your code.

    /*
     * serial_omap_get_divisor - calculate divisor value
     * @port: uart port info
     * @baud: baudrate for which divisor needs to be calculated.
     *
     * We have written our own function to get the divisor so as to support
     * 13x mode. 3Mbps Baudrate as an different divisor.
     * Reference OMAP TRM Chapter 17:
     * Table 17-1. UART Mode Baud Rates, Divisor Values, and Error Rates
     * referring to oversampling - divisor value
     * baudrate 460,800 to 3,686,400 all have divisor 13
     * except 3,000,000 which has divisor value 16
     */
    static unsigned int
    serial_omap_get_divisor(struct uart_port *port, unsigned int baud)
    {
        unsigned int divisor;

        if (baud > OMAP_MODE13X_SPEED && baud != 3000000)
            divisor = 13;
        else
            divisor = 16;
        return port->uartclk/(baud * divisor);
    }

    and some bits from the termio

    static void
    serial_omap_set_termios(struct uart_port *port, struct ktermios *termios,
                struct ktermios *old)
    {
    ...
        if (baud > 230400 && baud != 3000000)
            serial_out(up, UART_OMAP_MDR1, OMAP_MDR1_MODE13X);
        else
            serial_out(up, UART_OMAP_MDR1, OMAP_MDR1_MODE16X);
    ...
        spin_unlock_irqrestore(&up->port.lock, flags);
        dev_dbg(up->port.dev, "serial_omap_set_termios+%d\n", up->pdev->id);
    }

    That looks to be the only places where the 13X vs 16X mode is considered.

  • Thanks to both Norman and Prakash.

    The following mods to drivers/serial/8250.c worked, tested for 230,400 ; 460,800 ; and 921,600 baud.  (My lsz transfer (see old post from me) works, but the net speed is not full, so there lots of errors occurring.  But it's taking roughly 4 minutes instead of 20, or a 5x improvement over 115,200; implying a little better net than 460,800 would give me.)

    EDIT: Sorry, I can't seem to make indentation work right on forum. Using Google Chrome browser.

    /* Uart divisor latch write */
    static inline void _serial_dl_write(struct uart_8250_port *up, int value)
    {
    serial_outp(up, UART_DLL, value & 0xff);
    serial_outp(up, UART_DLM, value >> 8 & 0xff);

    // HgF - For DM6467 13x uart mode. Not checking any config or other define to enable, out of fear of losing it later in my customized code.
    {
    unsigned int oldMDR1, newMDR1;
    #define DM6467_13xMODE_HINT (0x4000)
    #define DM6467_MDR1_REG (0x8)
    #define DM6467_MODESELECT_BITS (0x7)
    #define DM6467_16xMODE (0x0)
    #define DM6467_13xMODE (0x3)
    oldMDR1 = serial_inp(up, DM6467_MDR1_REG);
    if (DM6467_13xMODE_HINT & value) { // 13x mode
    newMDR1 = (oldMDR1 & ~DM6467_MODESELECT_BITS) | DM6467_13xMODE;
    } else { // 16x mode
    newMDR1 = (oldMDR1 & ~DM6467_MODESELECT_BITS) | DM6467_16xMODE;
    }
    serial_outp(up, DM6467_MDR1_REG, newMDR1);
    //? Don't know why, but this printk mucks things up, so comment it out.
    //? printk(KERN_INFO "Serial: serial_dl_write, oldMDR1=%04x, newMDR1=%04x (HgF)\n", (int)oldMDR1, (int)newMDR1);
    }
    }
    
    
    static unsigned int serial8250_get_divisor(struct uart_port *port, unsigned int baud)
    {
    unsigned int quot;

    /*
    * Handle magic divisors for baud rates above baud_base on
    * SMSC SuperIO chips.
    */
    if ((port->flags & UPF_MAGIC_MULTIPLIER) &&
    baud == (port->uartclk/4))
    quot = 0x8001;
    else if ((port->flags & UPF_MAGIC_MULTIPLIER) &&
    baud == (port->uartclk/8))
    quot = 0x8002;
    else if (baud >= 230400 && baud != 3000000) // HgF - For DM6467 13x uart mode. Not checking any config or other define to enable, out of fear of losing it later in my customized code.
    quot = DM6467_13xMODE_HINT + (((port->uartclk/13)+(baud/2))/baud); // HgF - For DM6467 13x uart mode. Move /13 inward, so we're not risking overflow multiplying baud by 13.
    else
    quot = uart_get_divisor(port, baud);

    printk(KERN_INFO "Serial: serial8250_get_divisor, baud=%d, quot=%d (HgF)\n", (int)baud, (int)quot);

    return quot;
    }

  • Not sure about this. Will your DM6467_13xMODE_HINT (0x4000) bit get written to the UART_DLM register?

    I would guess you can't printk whiile configuring the serial port you are printing out on?

  • The HINT is fine.  There was a pre-existing "& 0xff" before sending to UART_DLM.  You can see it in the green if you look again.

    Regarding printk, it's indeed wierd.  I put one inside serial8250_get_divisor() and it's not a problem.  The one inside _serial_dl_write() I thought worked once, but perhaps I'm mistaken.  It causes gibberish to come out on the console, even while the baud rate is being set over and over to the default 115,200 during boot up.  Simply omitting the printk removes the gibberish.  Since things are being set to the SAME thing over and over, it shouldn't be a case of the printk occurring, and being sent out, while the setting are only half-set.  I just looked and it IS the case that serial8250_get_divisor() and its printk occur outside of a spin lock, but _serial_dl_write() and its printk occur INSIDE a spin lock that disables interrupts.  So perhaps it's that the printk can't be called while interrupts are disabled...

    Whatever, I got it working.  Testing showed a 6x decrease in lsz transfer time, vs the 8x decrease in the 921600's baud's bit time.  So, as I mentioned, the error rate goes up, but not nearly as much as the transfer speed, and more than I would get at 460,800 baud.  So I'm happy.

  • Still not quite seeing the HINT bit being masked out. The line

    serial_outp(up, UART_DLM, value >> 8 & 0xff);

    would seem to write 0x4000 >> 8 & 0xff which would be 0x40?

    Vaguely remember that the 8250 registers are bank switched. The DLM and DLL registers are in another bank, other registers might not be available. Might mess with the proper transmission. Or something like that. It all works. That's all that matters.

  • OK, Norman...  I don't know why I didn't see it before, but I see it now.  The code does appear to send 0x40 to UART_DLM when it should send 0x00.  Of course, I can't printk to test.  Thanks for keeping on top of this.

    I figured out why it works, nevertheless.  spruer6d.pdf section 3.5 points out that the DLH (aka UART_DLM) register uses only the lower 6 bits.  The 0x40 is BARELY out of that range... What luck!  Good thing I didn't make the hing 0x20 instead.    BTW, this means max DL is 0x3FFF.  In 16x mode and 24MHz clock, this means 91.5 baud.  So it can go down to 110 baud standard, if you want.  

    Note those higher bits are reserved.  Some future rev might add bits to go even faster, and then my prior software mod, if used by someone on such a processor, would begin to fail.

    Anyway, I modified that function as follows.  It still works, and no long relies on DLH using only 6 bits.  (Still won't indent properly on forum, I assume.)


    /* Uart divisor latch write */
    static inline void _serial_dl_write(struct uart_8250_port *up, int value)
    {
    // HgF - For DM6467 13x uart mode. Not checking any config or other define to enable, out of fear of losing it later in my customized code.
    {
    unsigned int oldMDR1, newMDR1;
    #define DM6467_13xMODE_HINT (0x4000)
    #define DM6467_MDR1_REG (0x8)
    #define DM6467_MODESELECT_BITS (0x7)
    #define DM6467_16xMODE (0x0)
    #define DM6467_13xMODE (0x3)
    oldMDR1 = serial_inp(up, DM6467_MDR1_REG);
    if (DM6467_13xMODE_HINT & value) { // 13x mode
    newMDR1 = (oldMDR1 & ~DM6467_MODESELECT_BITS) | DM6467_13xMODE;
    value = value & ~DM6467_13xMODE_HINT; // Remove HINT so it won't mess up UART_DLM, bug noticed by NW, even though things worked without it (maybe chip ignores higher bits)
    } else { // 16x mode
    newMDR1 = (oldMDR1 & ~DM6467_MODESELECT_BITS) | DM6467_16xMODE;
    }
    serial_outp(up, DM6467_MDR1_REG, newMDR1);
    }

    serial_outp(up, UART_DLL, value & 0xff);
    serial_outp(up, UART_DLM, value >> 8 & 0xff);
    }
  • On the ARM, "int" is 32-bits. You could move the HINT into the upper 16-bits, eg. 0x10000, leaving the bottom 16-bits for DL. Avoid the in-band signalling.

  • Good idea, especially for others wanting to adapt this thread to their own use.  For me, I may or may not do this, since my implementation works and is now safe for future chip upgrade.  It only disallows anything slower than 110 baud - whoop de do. (How DO you spell that?)

    If I do upgrade, I think I'll leave my hint-removal logic and merely move the hint bit position.  Then it's doubly safe.

    More importantly, thanks very much for your overall help and guidance on this.