Skip to content
Permalink
Browse files
serial: 8250: Add proper clock handling for OxSemi PCIe devices
Oxford Semiconductor PCIe (Tornado) serial port devices are driven by a
fixed 62.5MHz clock input derived from the 100MHz PCI Express clock.

We currently drive the device using its default oversampling rate of 16
and the clock prescaler disabled, consequently yielding the baud base of
3906250.  This base is inadequate for some of the high-speed baud rates
such as 460800bps, for which the closest rate possible can be obtained
by dividing the baud base by 8, yielding the baud rate of 488281.25bps,
which is off by 5.9638%.  This is enough for data communication to break
with the remote end talking actual 460800bps where missed stop bits have
been observed.

We can do better however, by taking advantage of a reduced oversampling
rate, which can be set to any integer value from 4 to 16 inclusive by
programming the TCR register, and by using the clock prescaler, which
can be set to any value from 1 to 63.875 in increments of 0.125 in the
CPR/CPR2 register pair[1][2][3][4].  The prescaler has to be explicitly
enabled though by setting bit 7 in the MCR or otherwise it is bypassed
as if the value of 1 was used.

By using these parameters rates from 15625000bps down to 1bps can be
obtained, with either exact or highly-accurate actual bit rates for
standard and many non-standard rates.

Make use of these features then as follows:

- Set the baud base to 15625000, reflecting the minimum oversampling
  rate of 4 with the clock prescaler and divisor both set to 1.

- Set the MCR mask and force bits in the UART template so as to have
  MCR[7] always set and then have 8250 core propagate those settings, if
  supplied as non-zero, overriding the ALPHA_KLUDGE_MCR default.

- Override the `get_divisor' handler and determine a good combination of
  parameters by using a lookup table with predetermined value pairs of
  the oversampling rate and the clock prescaler and finding a pair that
  divides the input clock such that the quotient, when rounded to the
  nearest integer, deviates the least from the exact result.  Calculate
  the clock divisor accordingly.

  Scale the resulting oversampling rate (only by powers of two) if
  possible so as to maximise it, reducing the divisor accordingly, and
  avoid a divisor overflow for very low baud rates by scaling the
  oversampling rate and/or the prescaler even if that causes some
  accuracy loss.

  Also handle the historic spd_cust feature so as to allow one to set
  all the three parameters manually to arbitrary values, by keeping the
  low 16 bits for the divisor and then putting TCR in bits 19:16 and
  CPR/CPR2 in bits 28:20, sanitising the bit pattern supplied such as
  to clamp CPR/CPR2 values between 0.000 and 0.875 inclusive to 1.000.
  This preserves compatibility with any existing setups, that is where
  requesting a custom divisor that only has any bits set among the low
  16 the oversampling rate of 16 and the clock prescaler of 1 will be
  used.

  Finally abuse the `frac' argument to store the determined bit patterns
  for the TCR, CPR and CPR2 registers.

- Override the `set_divisor' handler so as to set the TCR, CPR and CPR2
  registers from the `frac' value supplied.  Set the divisor as usually.

With the baud base set to 15625000 and the unsigned 16-bit UART_DIV_MAX
limitation imposed by `serial8250_get_baud_rate' standard baud rates
below 300bps become unavailable in the regular way, e.g. the rate of
200bps requires the baud base to be divided by 78125 and that is beyond
the unsigned 16-bit range.  The historic spd_cust feature can still be
used to obtain such rates if so required.

Here are the figures for the standard and some non-standard baud rates
(including those quoted in Oxford Semiconductor documentation), giving
the requested rate (r), the actual rate yielded (a) and its deviation
from the requested rate (d), and the values of the oversampling rate
(tcr), the clock prescaler (cpr) and the divisor (div) produced by the
new `get_divisor' handler:

r: 15625000, a: 15625000.00, d:  0.0000%, tcr:  4, cpr:  1.000, div:     1
r: 12500000, a: 12500000.00, d:  0.0000%, tcr:  5, cpr:  1.000, div:     1
r: 10416666, a: 10416666.67, d:  0.0000%, tcr:  6, cpr:  1.000, div:     1
r:  8928571, a:  8928571.43, d:  0.0000%, tcr:  7, cpr:  1.000, div:     1
r:  7812500, a:  7812500.00, d:  0.0000%, tcr:  8, cpr:  1.000, div:     1
r:  4000000, a:  4000000.00, d:  0.0000%, tcr:  5, cpr:  3.125, div:     1
r:  3686400, a:  3676470.59, d: -0.2694%, tcr:  8, cpr:  2.125, div:     1
r:  3500000, a:  3496503.50, d: -0.0999%, tcr: 13, cpr:  1.375, div:     1
r:  3000000, a:  2976190.48, d: -0.7937%, tcr: 14, cpr:  1.500, div:     1
r:  2500000, a:  2500000.00, d:  0.0000%, tcr: 10, cpr:  2.500, div:     1
r:  2000000, a:  2000000.00, d:  0.0000%, tcr: 10, cpr:  3.125, div:     1
r:  1843200, a:  1838235.29, d: -0.2694%, tcr: 16, cpr:  2.125, div:     1
r:  1500000, a:  1492537.31, d: -0.4975%, tcr:  5, cpr:  8.375, div:     1
r:  1152000, a:  1152073.73, d:  0.0064%, tcr: 14, cpr:  3.875, div:     1
r:   921600, a:   919117.65, d: -0.2694%, tcr: 16, cpr:  2.125, div:     2
r:   576000, a:   576036.87, d:  0.0064%, tcr: 14, cpr:  3.875, div:     2
r:   460800, a:   460829.49, d:  0.0064%, tcr:  7, cpr:  3.875, div:     5
r:   230400, a:   230414.75, d:  0.0064%, tcr: 14, cpr:  3.875, div:     5
r:   115200, a:   115207.37, d:  0.0064%, tcr: 14, cpr:  1.250, div:    31
r:    57600, a:    57603.69, d:  0.0064%, tcr:  8, cpr:  3.875, div:    35
r:    38400, a:    38402.46, d:  0.0064%, tcr: 14, cpr:  3.875, div:    30
r:    19200, a:    19201.23, d:  0.0064%, tcr:  8, cpr:  3.875, div:   105
r:     9600, a:     9600.06, d:  0.0006%, tcr:  9, cpr:  1.125, div:   643
r:     4800, a:     4799.98, d: -0.0004%, tcr:  7, cpr:  2.875, div:   647
r:     2400, a:     2400.02, d:  0.0008%, tcr:  9, cpr:  2.250, div:  1286
r:     1200, a:     1200.00, d:  0.0000%, tcr: 14, cpr:  2.875, div:  1294
r:      300, a:      300.00, d:  0.0000%, tcr: 11, cpr:  2.625, div:  7215
r:      200, a:      200.00, d:  0.0000%, tcr: 16, cpr:  1.250, div: 15625
r:      150, a:      150.00, d:  0.0000%, tcr: 13, cpr:  2.250, div: 14245
r:      134, a:      134.00, d:  0.0000%, tcr: 11, cpr:  2.625, div: 16153
r:      110, a:      110.00, d:  0.0000%, tcr: 12, cpr:  1.000, div: 47348
r:       75, a:       75.00, d:  0.0000%, tcr:  4, cpr:  5.875, div: 35461
r:       50, a:       50.00, d:  0.0000%, tcr: 16, cpr:  1.250, div: 62500
r:       25, a:       25.00, d:  0.0000%, tcr: 16, cpr:  2.500, div: 62500
r:        4, a:        4.00, d:  0.0000%, tcr: 16, cpr: 20.000, div: 48828
r:        2, a:        2.00, d:  0.0000%, tcr: 16, cpr: 40.000, div: 48828
r:        1, a:        1.00, d:  0.0000%, tcr: 16, cpr: 63.875, div: 61154

References:

[1] "OXPCIe200 PCI Express Multi-Port Bridge", Oxford Semiconductor,
    Inc., DS-0045, 10 Nov 2008, Section "950 Mode", pp. 64-65

[2] "OXPCIe952 PCI Express Bridge to Dual Serial & Parallel Port",
    Oxford Semiconductor, Inc., DS-0046, Mar 06 08, Section "950 Mode",
    p. 20

[3] "OXPCIe954 PCI Express Bridge to Quad Serial Port", Oxford
    Semiconductor, Inc., DS-0047, Feb 08, Section "950 Mode", p. 20

[4] "OXPCIe958 PCI Express Bridge to Octal Serial Port", Oxford
    Semiconductor, Inc., DS-0048, Feb 08, Section "950 Mode", p. 20

Signed-off-by: Maciej W. Rozycki <macro@orcam.me.uk>
  • Loading branch information
Maciej W. Rozycki authored and intel-lab-lkp committed Jun 16, 2021
1 parent f4f126f commit d3a55e8397b71f343ef931cd098d42a13faf9047
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 88 deletions.
@@ -120,6 +120,29 @@ static inline void serial_out(struct uart_8250_port *up, int offset, int value)
up->port.serial_out(&up->port, offset, value);
}

/*
* For the 16C950
*/
static inline void serial_icr_write(struct uart_8250_port *up,
int offset, int value)
{
serial_out(up, UART_SCR, offset);
serial_out(up, UART_ICR, value);
}

static inline unsigned int serial_icr_read(struct uart_8250_port *up,
int offset)
{
unsigned int value;

serial_icr_write(up, UART_ACR, up->acr | UART_ACR_ICRRD);
serial_out(up, UART_SCR, offset);
value = serial_in(up, UART_ICR);
serial_icr_write(up, UART_ACR, up->acr);

return value;
}

void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p);

static inline int serial_dl_read(struct uart_8250_port *up)
@@ -1020,6 +1020,12 @@ int serial8250_register_8250_port(struct uart_8250_port *up)
uart->rs485_stop_tx = up->rs485_stop_tx;
uart->dma = up->dma;

/* Override ALPHA_KLUDGE_MCR default. */
if (up->mcr_mask | up->mcr_force) {
uart->mcr_mask = up->mcr_mask;
uart->mcr_force = up->mcr_force;
}

/* Take tx_loadsz from fifosize if it wasn't set separately */
if (uart->port.fifosize && !uart->tx_loadsz)
uart->tx_loadsz = uart->port.fifosize;

0 comments on commit d3a55e8

Please sign in to comment.