2005-09-09 01:31:26

by Mathias Adam

[permalink] [raw]
Subject: [PATCH] 8250.c: Fix to make 16C950 UARTs work

Currently serial8250_set_termios() refuses to program a baud rate larger
than uartclk/16. However the 16C950 supports baud rates up to uartclk/4.
This worked already with Linux 2.4 so the biggest part of this patch was
simply taken from there and adapted to 2.6.

I needed this to get a Socket Bluetooth CF Card to work with BlueZ under
2.6 (the card did work under 2.4 already).

I posted the patch a while ago on the BlueZ mailing list and got reports
that it works as it should for a number of people so one could consider
including it into the standard kernel - opinions?

Please CC me as I'm not subscribed to the list.

Mathias Adam


--- linux-2.6.13-org/drivers/serial/8250.c 2005-08-29 01:41:01.000000000 +0200
+++ linux-2.6.13/drivers/serial/8250.c 2005-09-09 02:16:49.000000000 +0200
@@ -1665,7 +1665,7 @@
struct uart_8250_port *up = (struct uart_8250_port *)port;
unsigned char cval, fcr = 0;
unsigned long flags;
- unsigned int baud, quot;
+ unsigned int baud, quot, max_baud;

switch (termios->c_cflag & CSIZE) {
case CS5:
@@ -1697,9 +1697,28 @@
/*
* Ask the core to calculate the divisor for us.
*/
- baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
+ MAX_baud = (up->port.type == PORT_16C950 ? port->uartclk/4 : port->uartclk/16);
+ baud = uart_get_baud_rate(port, termios, old, 0, max_baud);
quot = serial8250_get_divisor(port, baud);

+ /*
+ * 16C950 supports additional prescaler ratios between 1:16 and 1:4
+ * thus increasing max baud rate to uartclk/4. The following was taken
+ * from kernel 2.4 by Mathias Adam <[email protected]> to make the Socket
+ * Bluetooth CF Card work under 2.6.13.
+ */
+ if (up->port.type == PORT_16C950) {
+ unsigned int baud_base = port->uartclk/16;
+ if (baud <= port->uartclk/16)
+ serial_icr_write(up, UART_TCR, 0);
+ else if (baud <= port->uartclk/8) {
+ serial_icr_write(up, UART_TCR, 0x8);
+ } else if (baud <= port->uartclk/4) {
+ serial_icr_write(up, UART_TCR, 0x4);
+ } else
+ serial_icr_write(up, UART_TCR, 0);
+ }
+
/*
* Oxford Semi 952 rev B workaround
*/


2005-09-09 01:50:44

by Stefan Smietanowski

[permalink] [raw]
Subject: Re: [PATCH] 8250.c: Fix to make 16C950 UARTs work

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Mathias Adam wrote:
> Currently serial8250_set_termios() refuses to program a baud rate larger
> than uartclk/16. However the 16C950 supports baud rates up to uartclk/4.
> This worked already with Linux 2.4 so the biggest part of this patch was
> simply taken from there and adapted to 2.6.
> - unsigned int baud, quot;
> + unsigned int baud, quot, max_baud;
^^^^^^^^
> - baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
> + MAX_baud = (up->port.type == PORT_16C950 ? port->uartclk/4 : port->uartclk/16);
^^^^^^^^

Did you even compile test this?

// Stefan
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (MingW32)

iD8DBQFDIOxFBrn2kJu9P78RAnG3AJ9EJKl6q4Q4+jXRdMifvmOEdO+HewCfUPd8
T2qQREDAgUq2C7j9yfaPemQ=
=hGK0
-----END PGP SIGNATURE-----

2005-09-09 02:48:53

by Mathias Adam

[permalink] [raw]
Subject: Re: [PATCH] 8250.c: Fix to make 16C950 UARTs work

Stefan Smietanowski wrote:
> Mathias Adam wrote:
> > Currently serial8250_set_termios() refuses to program a baud rate larger
> > than uartclk/16. However the 16C950 supports baud rates up to uartclk/4.
> > This worked already with Linux 2.4 so the biggest part of this patch was
> > simply taken from there and adapted to 2.6.
> > - unsigned int baud, quot;
> > + unsigned int baud, quot, max_baud;
> ^^^^^^^^
> > - baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
> > + MAX_baud = (up->port.type == PORT_16C950 ? port->uartclk/4 : port->uartclk/16);
> ^^^^^^^^
>
> Did you even compile test this?

Oops, I really really wonder how THIS could have happened as I just attached
my existing patch file (which was and still is correct) without touching
it - at least that's what I thought... Sorry!


Mathias Adam

I hope everything's alright this time:

--- linux-2.6.13-org/drivers/serial/8250.c 2005-08-29 01:41:01.000000000 +0200
+++ linux-2.6.13/drivers/serial/8250.c 2005-09-09 02:16:49.000000000 +0200
@@ -1665,7 +1665,7 @@
struct uart_8250_port *up = (struct uart_8250_port *)port;
unsigned char cval, fcr = 0;
unsigned long flags;
- unsigned int baud, quot;
+ unsigned int baud, quot, max_baud;

switch (termios->c_cflag & CSIZE) {
case CS5:
@@ -1697,9 +1697,28 @@
/*
* Ask the core to calculate the divisor for us.
*/
- baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
+ max_baud = (up->port.type == PORT_16C950 ? port->uartclk/4 : port->uartclk/16);
+ baud = uart_get_baud_rate(port, termios, old, 0, max_baud);
quot = serial8250_get_divisor(port, baud);

+ /*
+ * 16C950 supports additional prescaler ratios between 1:16 and 1:4
+ * thus increasing max baud rate to uartclk/4. The following was taken
+ * from kernel 2.4 by Mathias Adam <[email protected]> to make the Socket
+ * Bluetooth CF Card work under 2.6.13.
+ */
+ if (up->port.type == PORT_16C950) {
+ unsigned int baud_base = port->uartclk/16;
+ if (baud <= port->uartclk/16)
+ serial_icr_write(up, UART_TCR, 0);
+ else if (baud <= port->uartclk/8) {
+ serial_icr_write(up, UART_TCR, 0x8);
+ } else if (baud <= port->uartclk/4) {
+ serial_icr_write(up, UART_TCR, 0x4);
+ } else
+ serial_icr_write(up, UART_TCR, 0);
+ }
+
/*
* Oxford Semi 952 rev B workaround
*/

2005-09-09 10:18:40

by Russell King

[permalink] [raw]
Subject: Re: [PATCH] 8250.c: Fix to make 16C950 UARTs work

A couple of comments - see below.

On Fri, Sep 09, 2005 at 04:49:27AM +0200, Mathias Adam wrote:
> --- linux-2.6.13-org/drivers/serial/8250.c 2005-08-29 01:41:01.000000000 +0200
> +++ linux-2.6.13/drivers/serial/8250.c 2005-09-09 02:16:49.000000000 +0200
> @@ -1665,7 +1665,7 @@
> struct uart_8250_port *up = (struct uart_8250_port *)port;
> unsigned char cval, fcr = 0;
> unsigned long flags;
> - unsigned int baud, quot;
> + unsigned int baud, quot, max_baud;
>
> switch (termios->c_cflag & CSIZE) {
> case CS5:
> @@ -1697,9 +1697,28 @@
> /*
> * Ask the core to calculate the divisor for us.
> */
> - baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
> + max_baud = (up->port.type == PORT_16C950 ? port->uartclk/4 : port->uartclk/16);
> + baud = uart_get_baud_rate(port, termios, old, 0, max_baud);
> quot = serial8250_get_divisor(port, baud);
>
> + /*
> + * 16C950 supports additional prescaler ratios between 1:16 and 1:4
> + * thus increasing max baud rate to uartclk/4. The following was taken
> + * from kernel 2.4 by Mathias Adam <[email protected]> to make the Socket
> + * Bluetooth CF Card work under 2.6.13.
> + */
> + if (up->port.type == PORT_16C950) {
> + unsigned int baud_base = port->uartclk/16;

baud_base appears unused.

> + if (baud <= port->uartclk/16)
> + serial_icr_write(up, UART_TCR, 0);
> + else if (baud <= port->uartclk/8) {
> + serial_icr_write(up, UART_TCR, 0x8);
> + } else if (baud <= port->uartclk/4) {
> + serial_icr_write(up, UART_TCR, 0x4);
> + } else
> + serial_icr_write(up, UART_TCR, 0);

baud can't be larger than port->uartclk/4 since you limited it above.

> + }
> +
> /*
> * Oxford Semi 952 rev B workaround
> */

--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 Serial core

2005-09-09 14:41:34

by Mathias Adam

[permalink] [raw]
Subject: Re: [PATCH] 8250.c: Fix to make 16C950 UARTs work

Russell King wrote:
> On Fri, Sep 09, 2005 at 04:49:27AM +0200, Mathias Adam wrote:
> > + if (up->port.type == PORT_16C950) {
> > + unsigned int baud_base = port->uartclk/16;
>
> baud_base appears unused.

you're right, it's not necessary anymore. (New patch below)

> > + if (baud <= port->uartclk/16)
> > + serial_icr_write(up, UART_TCR, 0);
> > + else if (baud <= port->uartclk/8) {
> > + serial_icr_write(up, UART_TCR, 0x8);
> > + } else if (baud <= port->uartclk/4) {
> > + serial_icr_write(up, UART_TCR, 0x4);
> > + } else
> > + serial_icr_write(up, UART_TCR, 0);
>
> baud can't be larger than port->uartclk/4 since you limited it above.

Those lines come from 2.4.29's drivers/char/serial.c (>= line 1686). I
left in the last "else" to have some fallback if uart_get_baud_rate()
would change its behaviour to something else (i.e. does allow baud to
be larger than max_baud). However this would lead to an incorrect baud
rate to be set anyway (as the maximum baud rate of 16C950 is uartclk/4),
so one could simply set the "/4" mode for everything larger than uartclk/8.

Btw, if you look at lines 1700-1701 of original 2.6.13's 8250.c:

baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
quot = serial8250_get_divisor(port, baud);

baud is limited to uartclk/16 here, but serial8250_get_divisor() tests
it for being uartclk/4 or uartclk/8...
I'm afraid that max_baud thing has to become somewhat more general, or
do I miss something?

Hmm and I don't get the point in the calculation of "quot" - is that
formula correct in every case? I'll look into that a little bit now.

Mathias Adam

PS: I'm now subscribed to the list.


--- linux-2.6.13-org/drivers/serial/8250.c 2005-08-29 01:41:01.000000000 +0200
+++ linux-2.6.13/drivers/serial/8250.c 2005-09-09 15:33:23.000000000 +0200
@@ -1665,7 +1665,7 @@
struct uart_8250_port *up = (struct uart_8250_port *)port;
unsigned char cval, fcr = 0;
unsigned long flags;
- unsigned int baud, quot;
+ unsigned int baud, quot, max_baud;

switch (termios->c_cflag & CSIZE) {
case CS5:
@@ -1697,9 +1697,25 @@
/*
* Ask the core to calculate the divisor for us.
*/
- baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
+ max_baud = (up->port.type == PORT_16C950 ? port->uartclk/4 : port->uartclk/16);
+ baud = uart_get_baud_rate(port, termios, old, 0, max_baud);
quot = serial8250_get_divisor(port, baud);

+ /*
+ * 16C950 supports additional prescaler ratios between 1:16 and 1:4
+ * thus increasing max baud rate to uartclk/4. The following was taken
+ * from kernel 2.4 by Mathias Adam <[email protected]> to make the Socket
+ * Bluetooth CF Card work under 2.6.13.
+ */
+ if (up->port.type == PORT_16C950) {
+ if (baud <= port->uartclk/16)
+ serial_icr_write(up, UART_TCR, 0);
+ else if (baud <= port->uartclk/8) {
+ serial_icr_write(up, UART_TCR, 0x8);
+ } else
+ serial_icr_write(up, UART_TCR, 0x4);
+ }
+
/*
* Oxford Semi 952 rev B workaround
*/

2005-09-16 12:10:55

by Mathias Adam

[permalink] [raw]
Subject: Re: [PATCH] 8250.c: Fix to make 16C950 UARTs work

I've reworked the patch a little. Now it should enable both the 230400 and
the 460800 baud rates on any serial port which is using a 16C95x UART.
However as my 16C950 device is part of a Bluetooth dongle I couldn't
test the 460800 baud rate myself (I am able to set this rate with stty
though).
Please, could someone who owns such a UART try this out?
Any other comments?

Regards
Mathias



--- linux-2.6.13-org/drivers/serial/8250.c 2005-08-29 01:41:01.000000000 +0200
+++ linux-2.6.13/drivers/serial/8250.c 2005-09-16 12:18:14.000000000 +0200
@@ -7,6 +7,9 @@
*
* Copyright (C) 2001 Russell King.
*
+ * 2005/09/16: Enabled higher baud rates for 16C95x.
+ * (Mathias Adam <[email protected]>)
+ *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
@@ -1652,6 +1655,14 @@
else if ((port->flags & UPF_MAGIC_MULTIPLIER) &&
baud == (port->uartclk/8))
quot = 0x8002;
+ /*
+ * For 16C950s UART_TCR is used in combination with divisor==1
+ * to achieve baud rates up to baud_base*4.
+ */
+ else if ((port->type == PORT_16C950) &&
+ baud > (port->uartclk/16))
+ quot = 1;
+
else
quot = uart_get_divisor(port, baud);

@@ -1665,7 +1676,7 @@
struct uart_8250_port *up = (struct uart_8250_port *)port;
unsigned char cval, fcr = 0;
unsigned long flags;
- unsigned int baud, quot;
+ unsigned int baud, quot, max_baud;

switch (termios->c_cflag & CSIZE) {
case CS5:
@@ -1697,7 +1708,8 @@
/*
* Ask the core to calculate the divisor for us.
*/
- baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
+ max_baud = (up->port.type == PORT_16C950 ? port->uartclk/4 : port->uartclk/16);
+ baud = uart_get_baud_rate(port, termios, old, 0, max_baud);
quot = serial8250_get_divisor(port, baud);

/*
@@ -1733,6 +1745,19 @@
*/
spin_lock_irqsave(&up->port.lock, flags);

+ /*
+ * 16C950 supports additional prescaler ratios between 1:16 and 1:4
+ * thus increasing max baud rate to uartclk/4.
+ */
+ if (up->port.type == PORT_16C950) {
+ if (baud == port->uartclk/4)
+ serial_icr_write(up, UART_TCR, 0x4);
+ else if (baud == port->uartclk/8)
+ serial_icr_write(up, UART_TCR, 0x8);
+ else
+ serial_icr_write(up, UART_TCR, 0);
+ }
+
/*
* Update the per-port timeout.
*/