
From: Petr Vandrovec <vandrove@vc.cvut.cz>

Patch below adds support for using different prescaler than 16 for 16c950
chips.  This is needed for using Fujitsu-Siemens Connect2Air compact-flash
card, which comes (apparently) with 806kHz clocks, and so you have to
program prescaler for division by 7, and DLAB to 1, to get 115200Bd.  

To get card properly running you also have to add lines below to
/etc/pcmcia/serial.opts so kernel knows that base speed is not 115200 but
50400 (50400 * 16 = 806400; 806400 / 7 = 115200).  As I've found no code
specifying baud_rate in serial_cs, I assume that specifying it in
serial.opts is right way to do this type of things.

Patch also fixes problem that for UPF_MAGIC_MULTIPLIER maximum possible
baud rate passed to uart code was uartclk / 16 while correct value for
these devices (and for 16c950) is uartclk / 4.

Patch also fixes problem that for UPF_MAGIC_MULTIPLIER devices with
baud_rate 19200 or 9600 spd_cust did not work correctly.  Not that such
devices exist, but we should not ignore spd_cust, user probably knows why
he asked for spd_cust.

serial.opts:

case "$MANFID-$FUNCID-$PRODID_1-$PRODID_2-$PRODID_3-$PRODID_4" in
'0279,950b-2-GPRS Modem---')
    SERIAL_OPTS="baud_base 50400"
    ;;
esac

Cc: David Woodhouse <dwmw2@infradead.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 drivers/serial/8250.c |   82 +++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 64 insertions(+), 18 deletions(-)

diff -puN drivers/serial/8250.c~serial-add-support-for-non-standard-xtals-to-16c950-driver drivers/serial/8250.c
--- devel/drivers/serial/8250.c~serial-add-support-for-non-standard-xtals-to-16c950-driver	2005-09-12 03:34:57.000000000 -0700
+++ devel-akpm/drivers/serial/8250.c	2005-09-12 03:34:57.000000000 -0700
@@ -1653,24 +1653,58 @@ static void serial8250_shutdown(struct u
 		serial_unlink_irq_chain(up);
 }
 
-static unsigned int serial8250_get_divisor(struct uart_port *port, unsigned int baud)
+static unsigned int serial8250_get_divisor(struct uart_port *port, unsigned int baud,
+					   unsigned int *prescaler)
 {
-	unsigned int quot;
-
-	/*
-	 * Handle magic divisors for baud rates above baud_base on
-	 * SMSC SuperIO chips.
+        /*
+	 * Use special handling only if user did not supply its own divider.
+	 * spd_cust is defined in terms of baud_base, so always use default
+	 * prescaler when spd_cust is requested.
 	 */
-	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
-		quot = uart_get_divisor(port, baud);
 
-	return quot;
+	*prescaler = 16;
+        if (baud != 38400 || (port->flags & UPF_SPD_MASK) != UPF_SPD_CUST) {
+		unsigned int quot = port->uartclk / baud;
+
+		/*
+		 * Handle magic divisors for baud rates above baud_base on
+		 * SMSC SuperIO chips.
+		 */
+		if (port->flags & UPF_MAGIC_MULTIPLIER) {
+			if (quot == 4) {
+				return 0x8001;
+			} else if (quot == 8) {
+				return 0x8002;
+			}
+		}
+		if (port->type == PORT_16C950) {
+			/*
+			 * This computes TCR value (4 to 16), not CPR value (which can
+			 * be between 1.000 and 31.875) - chip I have uses XTAL of
+			 * 806400Hz, and so a division by 7 is required to get 115200Bd.
+			 * I'm leaving CPR disabled for now, until someone will
+			 * hit even more exotic XTAL (it is needed to get 500kbps
+			 * or 1000kbps from 18.432MHz XTAL, but I have no device
+			 * which would benefit from doing that).
+			 *
+			 * If we can use divide by 16, use it.  Otherwise look for
+			 * better prescaler, from 15 to 4.  If quotient cannot
+			 * be divided by any integer value between 4 and 15, use 4.
+			 */
+			if (quot & 0x0F) {
+				unsigned int div;
+
+				for (div = 15; div > 4; div--) {
+					if (quot % div == 0) {
+						break;
+					}
+				}
+				*prescaler = div;
+				return quot / div;
+			}
+		}
+	}
+	return uart_get_divisor(port, baud);
 }
 
 static void
@@ -1680,7 +1714,7 @@ serial8250_set_termios(struct uart_port 
 	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, prescaler;
 
 	switch (termios->c_cflag & CSIZE) {
 	case CS5:
@@ -1712,8 +1746,13 @@ serial8250_set_termios(struct uart_port 
 	/*
 	 * Ask the core to calculate the divisor for us.
 	 */
-	baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); 
-	quot = serial8250_get_divisor(port, baud);
+
+	if (port->type == PORT_16C950 || (port->flags & UPF_MAGIC_MULTIPLIER)) {
+		baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/4);
+	} else {
+		baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
+	}
+	quot = serial8250_get_divisor(port, baud, &prescaler);
 
 	/*
 	 * Oxford Semi 952 rev B workaround
@@ -1817,6 +1856,13 @@ serial8250_set_termios(struct uart_port 
 	serial_outp(up, UART_DLM, quot >> 8);		/* MS of divisor */
 
 	/*
+	 * Program prescaler for 16C950 chips.
+	 */
+	if (up->port.type == PORT_16C950) {
+		serial_icr_write(up, UART_TCR, prescaler == 16 ? 0 : prescaler);
+	}
+
+	/*
 	 * LCR DLAB must be set to enable 64-byte FIFO mode. If the FCR
 	 * is written without DLAB set, this mode will be disabled.
 	 */
_
