2002-02-20 15:57:11

by fabrizio.gennari

[permalink] [raw]
Subject: [PATCH] Kernel support for 16C950's CPR register

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 <[email protected]> (Modified by Theodore Ts'o)
*
+ * 6/01: Baud base scaling for 16C950 UARTs with unusual clocks
+ * Ian Abbott <[email protected]>
+ *
+ * 6/01: Use 16C950 interrupt trigger levels (RTL and TTL registers).
+ * Ian Abbott <[email protected]>
+ *
+ * 1/02: Use 16C950's quad-speed mode by default
+ * Fabrizio Gennari <[email protected]>
+ *
+ *
*/

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


2002-02-21 23:46:52

by Jean Tourrilhes

[permalink] [raw]
Subject: Re: [PATCH] Kernel support for 16C950's CPR register

On Wed, Feb 20, 2002 at 04:56:03PM +0100, [email protected] 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