linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] Kernel support for 16C950's CPR register
@ 2002-02-20 15:56 fabrizio.gennari
  2002-02-21 23:46 ` Jean Tourrilhes
  0 siblings, 1 reply; 2+ messages in thread
From: fabrizio.gennari @ 2002-02-20 15:56 UTC (permalink / raw)
  To: linux-kernel; +Cc: rmk, jt, abbotti

The 16C950 serial port from Oxford Semiconductor has a special register, 
called CPR (Clock Predivisor Register). This increases in steps of 1/8, 
thus allowing a finer control on the generated baud rate than the ordinary 
divisor registers DLL and DLM, that only increase in steps of 1.

There are 3 patches for CPR:
1. one written by me
2. one written by Jean Tourrilhes
3. one written by Ian Abbott

Patches 1 and 3 are available at 
http://sourceforge.net/tracker/?atid=300310&group_id=310&func=browse . 
Patch 2 is available at 
http://www.geocrawler.com/archives/3/8352/2001/10/0/6948700/ .

1. My patch is crap :) Avoid
2. Jean Tourrilhes' patch does not use CPR if the requested speed is 
higher than 115200 b/s. This is fine if the clock frequency is an integral 
multiple of 16*115200=1843200, but otherwise it does not exploit CPR's 
potential of fine-tuning speed.
3. Ian Abbott's patch is by far the best

Enclosed is an improvement over Ian Abbott's patch. Ian's disables 
quad-speed mode if the requested speed is lower than the baud base, while 
this enables quad-speed mode by default. Here is why: let's say the UART 
clock has a nonstandard frequency of 16 MHz, therefore the baud base is 
1000000. A speed of 460800 b/s is requested. Ian's patch disables 
quad-speed mode because 460800<1000000, then computes the best value of 
CPR=17. The resulting speed is 1000000/(17/8)=470588 b/s. This patch 
enables quad-speed mode, then computes the best value of CPR=69. Therefore 
the resulting speed is (1000000*4)/(69/8)=463768 b/s.

Using quad-speed mode allows a better resolution in setting speed.

Fabrizio Gennari
Philips Research Monza
via G.Casati 23, 20052 Monza (MI), Italy
tel. +39 039 2037816, fax +39 039 2037800


diff -ruN linux-2.4.17/drivers/char/serial.c 
linux-patched/drivers/char/serial.c
--- linux-2.4.17/drivers/char/serial.c  Fri Dec 21 18:41:54 2001
+++ linux-patched/drivers/char/serial.c Fri Jan 18 11:23:02 2002
@@ -57,6 +57,16 @@
  * 10/00: add in optional software flow control for serial console.
  *       Kanoj Sarcar <kanoj@sgi.com>  (Modified by Theodore Ts'o)
  *
+ *  6/01: Baud base scaling for 16C950 UARTs with unusual clocks
+ *       Ian Abbott <abbotti@mev.co.uk>
+ *
+ *  6/01: Use 16C950 interrupt trigger levels (RTL and TTL registers).
+ *       Ian Abbott <abbotti@mev.co.uk>
+ *
+ *  1/02: Use 16C950's quad-speed mode by default
+ *        Fabrizio Gennari <fabrizio.gennari@philips.com>
+ *
+ *
  */
 
 static char *serial_version = "5.05c";
@@ -1277,6 +1287,11 @@
                serial_outp(info, UART_LCR, 0xBF);
                serial_outp(info, UART_EFR, UART_EFR_ECB);
                serial_outp(info, UART_LCR, 0);
+               /* Initialize interrupt trigger levels. */
+               serial_icr_write(info, UART_RTL, 1);
+               serial_icr_write(info, UART_TTL, 1);
+               info->ACR |= UART_ACR_TLENB;
+               serial_icr_write(info, UART_ACR, info->ACR);
        }
 
 #ifdef CONFIG_SERIAL_RSA
@@ -1610,6 +1625,109 @@
 #endif
 
 /*
+ * This routine generates a CPR pre-scaler and quot divisor combination
+ * for the 16C950/954.  The CPR register allows us to generate unusual 
baud
+ * rates from standard clocks or standard baud rates from unusual clocks 
more
+ * accurately, and also allows us to scale the input clock down if 
required.
+ * The input clock can be scaled down by any value in the range 1 to 
31.875
+ * in steps of 0.125. This is CPR value / 8.
+ *
+ * Returns a quot divisor value (or zero if baud out of range) and sets a
+ * corresponding CPR value by reference.
+ */
+static int calc950_cpr_quot(int baud_base, int baud, int dllbug, int 
tcrbug, int *scale_numer, int *cpr)
+{
+       unsigned mbaud_base, mbaud, d, ed;
+       unsigned x, q, a, err, best_err;
+       unsigned char sieve[256 / 8];
+       int best_quot, best_cpr;
+
+       *scale_numer = 4;
+       best_quot = 0;
+       best_cpr = 8;   /* Default value. */
+       if ((baud > 0) && (baud_base >= baud)) {
+               mbaud_base = 8 * baud_base;
+
+               /* Determine whether quad speed mode should be disabled */
+
+               ed = (mbaud_base + baud - 1) / baud;
+               if (ed > 65535*255){                /* Quad speed mode is 
too fast */
+                 if (!tcrbug && ed <=65535*255*2){ 
+                   *scale_numer = 2;               /* Dual speed mode */
+                   mbaud_base/=2;
+                   ed/=2;
+                 }
+                 else{
+                   *scale_numer = 1;               /* Normal speed mode 
*/
+                   mbaud_base/=4;
+                   ed/=4;
+                 }
+               }
+
+               /* Determine minimum and maximum CPR values to consider. 
*/
+
+               d = ed / 65535;
+               if (d < 8)
+                       d = 8;
+               else if ((mbaud_base / (d * baud)) > 65535)
+                       ++d;    /* Round up. */
+               if (ed > 255)
+                       ed = 255;
+
+               /*
+                * Scale our calculations up so we can compare errors to 
find
+                * the best CPR value.
+                */
+               x = (UINT_MAX - 255) / (2 * mbaud_base);
+               mbaud_base *= x;
+               mbaud = x * baud;
+               /*
+                * Search for best (or close to best) CPR value.  Rounding
+                * errors mean we may not find the absolute best, but we 
should
+                * get pretty close.  We will end up with the required CPR
+                * value in scale_denom.
+                *
+                * A sieve is used to eliminate a lot of redundant cases. 
I
+                * think the extra cycles to maintain this sieve more than 
pay
+                * for themselves by the time the loop terminates.
+                */
+               memset(sieve, 0, sizeof(sieve));
+               best_err = UINT_MAX;
+               for ( ; d <= ed; d++) {
+                       int skip_sieve = 0;
+                       if (sieve[d>>3] & (1<<(d&7)))
+                               continue;
+                       x = (2*mbaud_base + d) / (2*d);
+                       q = (2*x + mbaud) / (2*mbaud);
+                       /*
+                        * Work around a bug in the Oxford Semiconductor 
952
+                        * rev B chip which causes it to seriously 
miscalculate
+                        * baud rates when DLL is 0.
+                        */
+                       if (((q & 0xFF) == 0) && dllbug) {
+                               q++;
+                               skip_sieve = 1;
+                       }
+                       a = (2*x + q) / (2*q);
+                       err = (a > mbaud) ? (a - mbaud) : (mbaud - a);
+                       if (err < best_err) {
+                               best_err = err;
+                               best_cpr = d;
+                               best_quot = q;
+                               if (err == 0)
+                                       break;
+                       }
+                       if (skip_sieve)
+                               continue;
+                       for (x = 2*d; x <= ed; x += d)
+                               sieve[x>>3] |= (1<<(x&7));
+               }
+       }
+       *cpr = best_cpr;
+       return best_quot;
+}
+
+/*
  * This routine is called to set the UART divisor registers to match
  * the specified baud rate for a serial port.
  */
@@ -1617,6 +1735,7 @@
                         struct termios *old_termios)
 {
        int     quot = 0, baud_base, baud;
+       int     scale_numer, scale_denom, mcr_clk_scale, tcr, rtl;
        unsigned cflag, cval, fcr = 0;
        int     bits;
        unsigned long   flags;
@@ -1651,76 +1770,109 @@
                cval |= UART_LCR_SPAR;
 #endif
 
-       /* Determine divisor based on baud rate */
-       baud = tty_get_baud_rate(info->tty);
-       if (!baud)
-               baud = 9600;    /* B0 transition handled in rs_set_termios 
*/
 #ifdef CONFIG_SERIAL_RSA
        if ((info->state->type == PORT_RSA) &&
            (info->state->baud_base != SERIAL_RSA_BAUD_BASE) &&
            enable_rsa(info))
                info->state->baud_base = SERIAL_RSA_BAUD_BASE;
 #endif
+
+recalculate_quot:
+       /* Determine divisor based on baud rate */
+       baud = tty_get_baud_rate(info->tty);
+       if (!baud)
+               baud = 9600;    /* B0 transition handled in rs_set_termios 
*/
+recalculate_quot_fixed_baud:
        baud_base = info->state->baud_base;
-       if (info->state->type == PORT_16C950) {
-               if (baud <= baud_base)
-                       serial_icr_write(info, UART_TCR, 0);
-               else if (baud <= 2*baud_base) {
-                       serial_icr_write(info, UART_TCR, 0x8);
-                       baud_base = baud_base * 2;
-               } else if (baud <= 4*baud_base) {
-                       serial_icr_write(info, UART_TCR, 0x4);
-                       baud_base = baud_base * 4;
-               } else
-                       serial_icr_write(info, UART_TCR, 0);
-       }
+       scale_numer = 1;
+       scale_denom = 1;
+       mcr_clk_scale = 0;
+       tcr = 0;
        if (baud == 38400 &&
            ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST))
                quot = info->state->custom_divisor;
        else {
-               if (baud == 134)
-                       /* Special case since 134 is really 134.5 */
-                       quot = (2*baud_base / 269);
-               else if (baud)
-                       quot = baud_base / baud;
-       }
-       /* If the quotient is zero refuse the change */
-       if (!quot && old_termios) {
-               info->tty->termios->c_cflag &= ~CBAUD;
-               info->tty->termios->c_cflag |= (old_termios->c_cflag & 
CBAUD);
-               baud = tty_get_baud_rate(info->tty);
-               if (!baud)
-                       baud = 9600;
-               if (baud == 38400 &&
-                   ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST))
-                       quot = info->state->custom_divisor;
-               else {
+               if (info->state->type == PORT_16C950) {
+                       /*
+                        * Handle speeds higher than baud_base by using 
the
+                        * TCR register to divide the clock by 4 or 8 
instead
+                        * of the usual 16. OX16C952 rev B and OX16C954 
rev A
+                        * only support 4 and 16 properly.
+                        */
+                       if (baud <= 4*baud_base) {
+                               int sbaud_base, sbaud;
+                               int dllbug = (info->state->revision == 
0x9502);
+                               int tcrbug = ((info->state->revision == 
0x5201) || (info->state->revision != 0x5400));
+ 
+                               sbaud_base = baud_base * 4;
+                               sbaud = baud;
+                               if (baud == 134) {
+                                       /* Special case: 134 is really 
134.5 */
+                                       sbaud_base *= 2;
+                                       sbaud = 269;
+                               }
+                               quot = calc950_cpr_quot(sbaud_base, sbaud,
+                                                       dllbug, tcrbug, 
&scale_numer, &scale_denom);
+                               switch(scale_numer){
+                               case 4 : tcr = 4; break;
+                               case 2 : tcr = 8; break;
+                               default: tcr = 0; break; /* always = 1 */
+                               }
+
+                               if (scale_denom == 8)
+                                       /* CPR pre-scaling not required. 
*/
+                                       scale_denom = 1;
+                               else {
+                                       scale_numer *= 8;
+                                       mcr_clk_scale = 
UART_MCR_CLK_SCALE;
+                               }
+                       }
+               } else {
                        if (baud == 134)
                                /* Special case since 134 is really 134.5 
*/
                                quot = (2*baud_base / 269);
                        else if (baud)
                                quot = baud_base / baud;
+                       if (quot > 65535)
+                               /* Baud must be too small. */
+                               quot = 0;
                }
        }
+       /* If the quotient is zero refuse the change */
+       if (!quot && old_termios) {
+               info->tty->termios->c_cflag &= ~CBAUD;
+               info->tty->termios->c_cflag |= (old_termios->c_cflag & 
CBAUD);
+               goto recalculate_quot;
+       }
        /* As a last resort, if the quotient is zero, default to 9600 bps 
*/
-       if (!quot)
-               quot = baud_base / 9600;
-       /*
-        * Work around a bug in the Oxford Semiconductor 952 rev B
-        * chip which causes it to seriously miscalculate baud rates
-        * when DLL is 0.
-        */
-       if (((quot & 0xFF) == 0) && (info->state->type == PORT_16C950) &&
-           (info->state->revision == 0x5201))
-               quot++;
+       if (!quot) {
+               if (baud != 9600) {
+                       baud = 9600;
+                       goto recalculate_quot_fixed_baud;
+               } else {
+                       /* Paranoia steps in... */
+                       quot = baud_base / 9600;
+                       if (quot < 1)
+                               quot = 1;
+                       else if (quot > 65535)
+                               quot = 65535;
+                       scale_numer = 1;
+                       scale_denom = 1;
+                       mcr_clk_scale = 0;
+                       tcr = 0;
+               }
+       }
 
        info->quot = quot;
-       info->timeout = ((info->xmit_fifo_size*HZ*bits*quot) / baud_base);
+       info->scale_numer = scale_numer;
+       info->scale_denom = scale_denom;
+       info->timeout = ((info->xmit_fifo_size*HZ*bits*quot*scale_denom) /
+                        (baud_base*scale_numer));
        info->timeout += HZ/50;         /* Add .02 seconds of slop */
 
        /* Set up FIFO's */
        if (uart_config[info->state->type].flags & UART_USE_FIFO) {
-               if ((info->state->baud_base / quot) < 2400)
+               if (baud < 2400)
                        fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
 #ifdef CONFIG_SERIAL_RSA
                else if (info->state->type == PORT_RSA)
@@ -1731,6 +1883,13 @@
        }
        if (info->state->type == PORT_16750)
                fcr |= UART_FCR7_64BYTE;
+       /* 16C950 receiver interrupt trigger level. */
+       rtl = 1;
+       if (info->state->type == PORT_16C950) {
+               /* XXX This could probably use more tweaking... */
+               if (baud >= 2400)
+                       rtl = 32;
+       }
 
        /* CTS flow control flag and modem status interrupts */
        info->IER &= ~UART_IER_MSI;
@@ -1800,6 +1959,17 @@
                }
                serial_outp(info, UART_FCR, fcr);       /* set fcr */
        }
+       if (info->state->type == PORT_16C950) {
+               serial_icr_write(info, UART_TCR, tcr);
+               if (mcr_clk_scale)
+                       serial_icr_write(info, UART_CPR, scale_denom);
+               if ((info->MCR & UART_MCR_CLK_SCALE) != mcr_clk_scale) {
+                       info->MCR ^= UART_MCR_CLK_SCALE;
+                       serial_outp(info, UART_MCR, info->MCR);
+               }
+               if (fcr & UART_FCR_ENABLE_FIFO)
+                       serial_icr_write(info, UART_RTL, rtl);
+       }
        restore_flags(flags);
 }
 
@@ -3294,7 +3464,8 @@
 
        if (info->quot) {
                ret += sprintf(buf+ret, " baud:%d",
-                              state->baud_base / info->quot);
+                              ((state->baud_base * info->scale_numer) /
+                               (info->quot * info->scale_denom)));
        }
 
        ret += sprintf(buf+ret, " tx:%d rx:%d",
diff -ruN linux-2.4.17/include/linux/serialP.h 
linux-patched/include/linux/serialP.h
--- linux-2.4.17/include/linux/serialP.h        Thu Nov 22 20:47:23 2001
+++ linux-patched/include/linux/serialP.h       Fri Jan 18 10:51:29 2002
@@ -67,6 +67,8 @@
        int                     ignore_status_mask;
        int                     timeout;
        int                     quot;
+       int                     scale_numer;    /* Multiply baud_base by 
*/
+       int                     scale_denom;    /* scale_numer / 
scale_denom */
        int                     x_char; /* xon/xoff character */
        int                     close_delay;
        unsigned short          closing_wait;
diff -ruN linux-2.4.17/include/linux/serial_reg.h 
linux-patched/include/linux/serial_reg.h
--- linux-2.4.17/include/linux/serial_reg.h     Wed May  2 01:05:00 2001
+++ linux-patched/include/linux/serial_reg.h    Fri Jan 18 10:51:29 2002
@@ -126,6 +126,15 @@
 #define UART_MCR_OUT1  0x04    /* Out1 complement */
 #define UART_MCR_RTS   0x02    /* RTS complement */
 #define UART_MCR_DTR   0x01    /* DTR complement */
+/*
+ * Clock scaler for Startech and 16C950.
+ * This enables a divide by 4 on Startech.
+ * This enables a divide by M+N/8 on 16C950 (controlled by CPR register).
+ * Note the bit can only be modified when EFR-bit 4 is selected but the 
last
+ * value written remains active (but reads as 0) if EFR-bit 4 is 
subsequently
+ * unselected.
+ */
+#define UART_MCR_CLK_SCALE     0x80    /* Enable clock scaler */
 
 /*
  * These are the definitions for the Modem Status Register

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH] Kernel support for 16C950's CPR register
  2002-02-20 15:56 [PATCH] Kernel support for 16C950's CPR register fabrizio.gennari
@ 2002-02-21 23:46 ` Jean Tourrilhes
  0 siblings, 0 replies; 2+ messages in thread
From: Jean Tourrilhes @ 2002-02-21 23:46 UTC (permalink / raw)
  To: fabrizio.gennari; +Cc: linux-kernel, rmk, abbotti

On Wed, Feb 20, 2002 at 04:56:03PM +0100, fabrizio.gennari@philips.com wrote:
> The 16C950 serial port from Oxford Semiconductor has a special register, 
> called CPR (Clock Predivisor Register). This increases in steps of 1/8, 
> thus allowing a finer control on the generated baud rate than the ordinary 
> divisor registers DLL and DLM, that only increase in steps of 1.
> 
> There are 3 patches for CPR:
> 1. one written by me
> 2. one written by Jean Tourrilhes
> 3. one written by Ian Abbott
> 
> Patches 1 and 3 are available at 
> http://sourceforge.net/tracker/?atid=300310&group_id=310&func=browse . 
> Patch 2 is available at 
> http://www.geocrawler.com/archives/3/8352/2001/10/0/6948700/ .

	My patch is also available at :
		http://www.hpl.hp.com/personal/Jean_Tourrilhes/bt/

> 1. My patch is crap :) Avoid
> 2. Jean Tourrilhes' patch does not use CPR if the requested speed is 
> higher than 115200 b/s. This is fine if the clock frequency is an integral 
> multiple of 16*115200=1843200, but otherwise it does not exploit CPR's 
> potential of fine-tuning speed.
> 3. Ian Abbott's patch is by far the best

	Well... I disagree. My patches has advantage over Ian's patch :
		o autoset baud_base/CPR (Ian's patch doesn't)
		o allow to set custom MSR (Ian's patch doesn't)
		o much simpler. My code is readable, has minimal
impact on the general path (non 16C950) and doesn't have ugly gotos
all over the place.
	To claim that Ian's patch is the best, you would need to at
least fix 1 and 2. Number 3 is debatable.
	Regards,

	Jean

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2002-02-21 23:46 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2002-02-20 15:56 [PATCH] Kernel support for 16C950's CPR register fabrizio.gennari
2002-02-21 23:46 ` Jean Tourrilhes

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).