Hi,
I've noticed our Malta platform serial driver does not take advantage of
support for the special UART divisor values for the bit rates of 230400
and 460800 bits per second its SMSC FDC37M817 Super I/O hardware provides.
Our 8250 core provides for these divisors via the UPF_MAGIC_MULTIPLIER
flag, so I thought it would be a straightforward platform change, but it
has turned out that the flag has AFAICT never worked (I wonder how people
test those things). The only platform currently in our tree that sets the
flag uses it for a different purpose.
I've fixed UPF_MAGIC_MULTIPLIER handling then in our 8250 core, added
reporting so that people have a chance to know the rates are supported and
then added the flag to Malta platform initialisers for Super I/O UARTs.
I have examined YAMON sources to verify that we don't have to enable the
special UART divisor values ourselves. See individual change descriptions
for further details.
Verified with a WTI CPM-800 site manager device which supports bit rates
of up to 230400bps. Also verified at 230400bps and 460800bps with an
EXSYS EX-44072 option card under Linux, based on the Oxford Semiconductor
OXPCIe952 device in its native mode; our clock settings are however off
enough for OxSemi PCIe devices for the former data rate only just working
and the latter making transmission go out of sync with data corruption
resulting. Another patch series to fix OxSemi handling bugs follows then.
Verification of the OxSemi patch series actually revealed an interesting
peculiarity of the SMSC Super I/O IC that contradicts SMSC documentation.
I have added a clarification as 1/5 of this patch series, originally meant
to contain four patches only. This has been confirmed both with the Malta
board concerned here and my old x86 server, which also has a similar SMSC
Super I/O part, although with high-speed operation not enabled by the
BIOS.
NB occasional input overruns were observed at 460800bps (with a MIPS 5Kc
@160MHz running this Malta), which could be eliminated by lowering the
`rx_trig_bytes' parameter to 4 from the default of 8 (the UART core in the
FDC37M817 has a 16-byte FIFO), via sysfs. Perhaps it would make sense to
change the default, at least for the higher-speed ports?
Please apply.
Maciej
Contrary to what SMSC documentation says and unlike NS16C550A UARTs the
SMSC Super I/O IC claims compatibility with the SMSC UART implementation
does not support dividing the internal PLL clock by any divisor from 1
to 65535[1], with the exception of two magic divisors of 32769 and 32770
used respectively to select the high-speed data rates of 460800bps and
230400bps[2] if enabled on a port-by-port basis in with the Serial Port
Mode Register in the Device Configuration Space[3][4].
Instead empirical evidence indicates that the divisor, as stored in the
DLL and DLM register pair, has the range of 1 to 32767 only, and bit 7
of the DLM register (bit 15 of the divisor) effectively serves as a
selection bit for the prescaler from the base frequency of 7.3728MHz,
either 4 if the bit is 0, or 1 if the bit is 1 and high-speed operation
has been enabled with the Serial Port Mode Register.
So if high-speed operation has not been enabled, then say the values of
1 and 32769 (0x8001) written into the combined DLL and DLM register pair
both select the divisor of 1 and the baud rate of 115200bps.
[1] "FDC37M81x, PC98/99 Compliant Enhanced Super I/O Controller with
Keyboard/Mouse Wake-Up", Standard Microsystems Corporation, Rev.
03/27/2000, Section "Programmable Baud Rate Generator (and Divisor
Latches DLH, DLL)", p. 75
[2] same, Table 31 - "Baud Rates", p. 77
[3] same, Table 60 - "Serial Port 1, Logical Device 4 [Logical Device
Number = 0x04]", p. 153
[4] same, Table 61 - "Serial Port 2, Logical Device 5 [Logical Device
Number = 0x05]", p. 153
Signed-off-by: Maciej W. Rozycki <[email protected]>
---
drivers/tty/serial/8250/8250_port.c | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
linux-serial-8250-magic-multiplier-doc.diff
Index: linux-malta-cbus-uart/drivers/tty/serial/8250/8250_port.c
===================================================================
--- linux-malta-cbus-uart.orig/drivers/tty/serial/8250/8250_port.c
+++ linux-malta-cbus-uart/drivers/tty/serial/8250/8250_port.c
@@ -2519,9 +2519,36 @@ static unsigned int serial8250_do_get_di
unsigned int quot;
/*
- * Handle magic divisors for baud rates above baud_base on
- * SMSC SuperIO chips.
+ * Handle magic divisors for baud rates above baud_base on SMSC
+ * Super I/O chips. We clamp custom rates from clk/6 and clk/12
+ * up to clk/4 (0x8001) and clk/8 (0x8002) respectively. These
+ * magic divisors actually reprogram the baud rate generator's
+ * reference clock derived from chips's 14.318MHz clock input.
+ *
+ * Documentation claims that with these magic divisors the base
+ * frequencies of 7.3728MHz and 3.6864MHz are used respectively
+ * for the extra baud rates of 460800bps and 230400bps rather
+ * than the usual base frequency of 1.8462MHz. However empirical
+ * evidence contradicts that.
*
+ * Instead bit 7 of the DLM register (bit 15 of the divisor) is
+ * effectively used as a clock prescaler selection bit for the
+ * base frequency of 7.3728MHz, always used. If set to 0, then
+ * the base frequency is divided by 4 for use by the Baud Rate
+ * Generator, for the usual arrangement where the value of 1 of
+ * the divisor produces the baud rate of 115200bps. Conversely,
+ * if set to 1 and high-speed operation has been enabled with the
+ * Serial Port Mode Register in the Device Configuration Space,
+ * then the base frequency is supplied directly to the Baud Rate
+ * Generator, so for the divisor values of 0x8001, 0x8002, 0x8003,
+ * 0x8004, etc. the respective baud rates produced are 460800bps,
+ * 230400bps, 153600bps, 115200bps, etc.
+ *
+ * In all cases only low 15 bits of the divisor are used to divide
+ * the baud base and therefore 32767 is the maximum divisor value
+ * possible, even though documentation says that the programmable
+ * Baud Rate Generator is capable of dividing the internal PLL
+ * clock by any divisor from 1 to 65535.
*/
if ((port->flags & UPF_MAGIC_MULTIPLIER) &&
baud == (port->uartclk/4))
Handle custom baud rates requested in the UPF_MAGIC_MULTIPLIER range
with BOTHER. Currently matching is exact, that is if a baud rate that
is not either of clk/4 or clk/8 is requested, then we fall through to
the default case, which will just divide the clock rate by 16 times the
rate requested, round it to closest integer, and possibly yield even
worse results then if clamping to the extra baud rates was chosen.
So for example if we have the usual base rate of 115200 and request a
rate of 230399, then the fall-through divisor calculation will yield 1,
and consequently the baud rate of 115200 will be programmed even though
obviously the magic rate of 230400 would be more appropriate.
Make the selection of the magic rates range-qualified then and use clk/4
for rates from clk/6 up (assuming `serial8250_get_baud_rate' has already
rejected any rates too far beyond clk/4), and otherwise use clk/8 for
rates from clk/12 up, and finally fall through to the standard divisor
calculation. Explicitly void using the undocumented rate of 153600bps
and stick to documented divisor values only.
Signed-off-by: Maciej W. Rozycki <[email protected]>
---
drivers/tty/serial/8250/8250_port.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
linux-serial-8250-magic-multiplier-bother.diff
Index: linux-malta-cbus-uart/drivers/tty/serial/8250/8250_port.c
===================================================================
--- linux-malta-cbus-uart.orig/drivers/tty/serial/8250/8250_port.c
+++ linux-malta-cbus-uart/drivers/tty/serial/8250/8250_port.c
@@ -2515,6 +2515,7 @@ static unsigned int serial8250_do_get_di
unsigned int baud,
unsigned int *frac)
{
+ upf_t magic_multiplier = port->flags & UPF_MAGIC_MULTIPLIER;
struct uart_8250_port *up = up_to_u8250p(port);
unsigned int quot;
@@ -2550,11 +2551,9 @@ static unsigned int serial8250_do_get_di
* Baud Rate Generator is capable of dividing the internal PLL
* clock by any divisor from 1 to 65535.
*/
- if ((port->flags & UPF_MAGIC_MULTIPLIER) &&
- baud == (port->uartclk/4))
+ if (magic_multiplier && baud >= port->uartclk / 6)
quot = 0x8001;
- else if ((port->flags & UPF_MAGIC_MULTIPLIER) &&
- baud == (port->uartclk/8))
+ else if (magic_multiplier && baud >= port->uartclk / 12)
quot = 0x8002;
else if (up->port.type == PORT_NPCM)
quot = npcm_get_divisor(up, baud);
Support for magic baud rate divisors of 32770 and 32769 used with SMSC
Super I/O chips for extra baud rates of 230400 and 460800 respectively
where base rate is 115200[1] has been added around Linux 2.5.64, which
predates our repo history, but the origin could be identified as commit
2a717aad772f ("Merge with Linux 2.5.64.") with the old MIPS/Linux repo
also at: <git://git.kernel.org/pub/scm/linux/kernel/git/ralf/linux.git>.
Code that is now in `serial8250_do_get_divisor' was added back then to
`serial8250_get_divisor', but that code would only ever trigger if one
of the higher baud rates was actually requested, and that cannot ever
happen, because the earlier call to `serial8250_get_baud_rate' never
returns them. This is because it calls `uart_get_baud_rate' with the
maximum requested being the base rate, that is clk/16 or 115200 for SMSC
chips at their nominal clock rate.
Fix it then and allow UPF_MAGIC_MULTIPLIER baud rates to be selected, by
requesting the maximum baud rate of clk/4 rather than clk/16 if the flag
has been set. Also correct the minimum baud rate, observing that these
ports only support actual (non-magic) divisors of up to 32767 only.
References:
[1] "FDC37M81x, PC98/99 Compliant Enhanced Super I/O Controller with
Keyboard/Mouse Wake-Up", Standard Microsystems Corporation, Rev.
03/27/2000, Table 31 - "Baud Rates", p. 77
Signed-off-by: Maciej W. Rozycki <[email protected]>
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
---
drivers/tty/serial/8250/8250_port.c | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
linux-serial-8250-magic-multiplier-baud-rate.diff
Index: linux-malta-cbus-uart/drivers/tty/serial/8250/8250_port.c
===================================================================
--- linux-malta-cbus-uart.orig/drivers/tty/serial/8250/8250_port.c
+++ linux-malta-cbus-uart/drivers/tty/serial/8250/8250_port.c
@@ -2659,6 +2659,21 @@ static unsigned int serial8250_get_baud_
struct ktermios *old)
{
unsigned int tolerance = port->uartclk / 100;
+ unsigned int min;
+ unsigned int max;
+
+ /*
+ * Handle magic divisors for baud rates above baud_base on SMSC
+ * Super I/O chips. Enable custom rates of clk/4 and clk/8, but
+ * disable divisor values beyond 32767, which are unavailable.
+ */
+ if (port->flags & UPF_MAGIC_MULTIPLIER) {
+ min = port->uartclk / 16 / UART_DIV_MAX >> 1;
+ max = (port->uartclk + tolerance) / 4;
+ } else {
+ min = port->uartclk / 16 / UART_DIV_MAX;
+ max = (port->uartclk + tolerance) / 16;
+ }
/*
* Ask the core to calculate the divisor for us.
@@ -2666,9 +2681,7 @@ static unsigned int serial8250_get_baud_
* slower than nominal still match standard baud rates without
* causing transmission errors.
*/
- return uart_get_baud_rate(port, termios, old,
- port->uartclk / 16 / UART_DIV_MAX,
- (port->uartclk + tolerance) / 16);
+ return uart_get_baud_rate(port, termios, old, min, max);
}
/*
The SMSC FDC37M817 Super I/O chip has a configuration feature that lets
it support special UART divisor values of 32770 and 32769 for bit rates
of 230400 and 460800 bits per second respectively. Our 8250 driver core
provides support for these special divisors via the UPF_MAGIC_MULTIPLIER
flag, and YAMON firmware unconditionally configures the Super I/O chip
with these divisors enabled as well, so all we need to do in platform
setup for these bit rates to work is to set the flag.
Signed-off-by: Maciej W. Rozycki <[email protected]>
---
arch/mips/mti-malta/malta-platform.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
linux-mips-malta-smsc-uart-magic-multiplier.diff
Index: linux-malta-cbus-uart/arch/mips/mti-malta/malta-platform.c
===================================================================
--- linux-malta-cbus-uart.orig/arch/mips/mti-malta/malta-platform.c
+++ linux-malta-cbus-uart/arch/mips/mti-malta/malta-platform.c
@@ -33,7 +33,8 @@
.irq = int, \
.uartclk = 1843200, \
.iotype = UPIO_PORT, \
- .flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST, \
+ .flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST | \
+ UPF_MAGIC_MULTIPLIER, \
.regshift = 0, \
}